Filtering
Hot Chocolate generates filter input types from your .NET models and translates client-supplied filter arguments into native database queries. The default implementation builds expression trees that apply to IQueryable, but you can customize filters for other data sources.
Given a model like User with a Name property, Hot Chocolate generates a UserFilterInput with string operations such as eq, contains, startsWith, and more. Clients use the where argument to filter results.
Getting Started#
Filtering is part of the HotChocolate.Data package.
dotnet add package HotChocolate.DataHotChocolate.* packages need to have the same version.Register filtering on the schema:
builder
.AddGraphQL()
.AddFiltering();Apply the [UseFiltering] attribute to a resolver that returns IQueryable<T> or IEnumerable<T>:
Clients can now filter users:
query {
users(where: { name: { contains: "Alice" } }) {
name
email
}
}Middleware order matters. When combining multiple middleware, apply them in this order: UsePaging > UseProjection > UseFiltering > UseSorting.
Filter Types#
Hot Chocolate generates filter operations based on the .NET type of each property.
String Filters#
Operations: eq, neq, contains, ncontains, in, nin, startsWith, nstartsWith, endsWith, nendsWith.
Boolean Filters#
Operations: eq, neq.
Comparable Filters#
For numeric types (int, long, float, double, decimal), Guid, DateTime, DateTimeOffset, and TimeSpan.
Operations: eq, neq, in, nin, gt, ngt, gte, ngte, lt, nlt, lte, nlte.
Enum Filters#
Operations: eq, neq, in, nin.
Object Filters#
Filters are generated for nested objects, supporting filtering across database relationships:
public class User
{
public string Name { get; set; }
public Address Address { get; set; }
}
public class Address
{
public string City { get; set; }
}query {
users(where: { address: { city: { eq: "Berlin" } } }) {
name
}
}List Filters#
For collection properties, Hot Chocolate generates all, none, some, and any operations:
query {
users(where: { orders: { some: { total: { gt: 100 } } } }) {
name
}
}Combining Filters with "and" / "or"#
Every filter input type includes and and or fields for composing multiple conditions:
query {
users(
where: {
or: [{ name: { contains: "Alice" } }, { name: { contains: "Bob" } }]
}
) {
name
}
}The or field must be used at the top level of the filter. Placing it inside a field operation results in and semantics instead.
Removing "and" / "or"#
Disable these combinators in a custom filter type:
public class UserFilterType : FilterInputType<User>
{
protected override void Configure(IFilterInputTypeDescriptor<User> descriptor)
{
descriptor.AllowAnd(false).AllowOr(false);
}
}Custom Filter Types#
Customize which fields are filterable and which operations are available by extending FilterInputType<T>:
public class UserFilterType : FilterInputType<User>
{
protected override void Configure(IFilterInputTypeDescriptor<User> descriptor)
{
descriptor.BindFieldsExplicitly();
descriptor.Field(f => f.Name);
descriptor.Field(f => f.Email).Type<CustomStringOperationFilterInputType>();
}
}Restrict operations on a field by defining a custom operation type:
public class CustomStringOperationFilterInputType : StringOperationFilterInputType
{
protected override void Configure(IFilterInputTypeDescriptor descriptor)
{
descriptor.Operation(DefaultFilterOperations.Equals).Type<StringType>();
descriptor.Operation(DefaultFilterOperations.Contains).Type<StringType>();
}
}Apply the custom filter type:
Filter Conventions#
Filter conventions let you change filtering behavior globally across your schema.
Setting Up a Convention#
Extend FilterConvention and override Configure, or use FilterConventionExtension to build on top of the defaults:
public class CustomFilterConvention : FilterConvention
{
protected override void Configure(IFilterConventionDescriptor descriptor)
{
descriptor.AddDefaults();
descriptor.ArgumentName("filter");
}
}builder
.AddGraphQL()
.AddConvention<IFilterConvention, CustomFilterConvention>();Binding Filter Types Globally#
Bind custom filter types to .NET types through the convention:
public class CustomFilterConvention : FilterConvention
{
protected override void Configure(IFilterConventionDescriptor descriptor)
{
descriptor.AddDefaults();
descriptor.BindRuntimeType<User, UserFilterType>();
}
}Custom Scalar Filters#
When you use custom scalars (including those from HotChocolate.Types.Scalars), you must create and bind a filter type for each scalar:
public class EmailAddressOperationFilterInputType : FilterInputType
{
protected override void Configure(IFilterInputTypeDescriptor descriptor)
{
descriptor.Operation(DefaultFilterOperations.Equals).Type<EmailAddressType>();
descriptor.Operation(DefaultFilterOperations.NotEquals).Type<EmailAddressType>();
descriptor.Operation(DefaultFilterOperations.Contains).Type<EmailAddressType>();
}
}builder
.AddGraphQL()
.AddFiltering(x => x
.AddDefaults()
.BindRuntimeType<string, EmailAddressOperationFilterInputType>());Next Steps#
- Need to sort results? See Sorting.
- Need to page through results? See Pagination.
- Need to optimize database queries? See Projections.
- Need to protect against expensive filter queries? See Cost Analysis.