QueryKit is a .NET library that makes it easier to query your data by providing a fluent and intuitive syntax for filtering and sorting.
$ dotnet add package QueryKitQueryKit is a .NET library that makes it easier to query your data by providing a fluent and intuitive syntax for filtering and sorting. It's inspired by the Sieve library but with some differences.
dotnet add package QueryKit
If we wanted to apply a filter to a DbSet called People, we just have to do something like this:
var filterInput = """FirstName == "Jane" && Age > 10""";
var people = _dbContext.People
.ApplyQueryKitFilter(filterInput)
.ToList();
QueryKit will automatically translate this into an expression for you. You can even customize your property names:
var filterInput = """first == "Jane" && Age > 10""";
var config = new QueryKitConfiguration(config =>
{
config.Property<Person>(x => x.FirstName).HasQueryName("first");
});
var people = _dbContext.People
.ApplyQueryKitFilter(filterInput, config)
.ToList();
Sorting works too:
var filterInput = """first == "Jane" && Age > 10""";
var config = new QueryKitConfiguration(config =>
{
config.Property<Person>(x => x.FirstName).HasQueryName("first");
});
var people = _dbContext.People
.ApplyQueryKitFilter(filterInput, config)
.ApplyQueryKitSort("first, Age desc", config)
.ToList();
And that's the basics! There's no services to inject or global set up to worry about, just apply what you want and call it a day. For a full list of capables, see below.
To apply filters to your queryable, you just need to pass an input string with your filtering input to ApplyQueryKitFilter off of a queryable:
var people = _dbContext.People.ApplyQueryKitFilter("Age < 10").ToList();
You can also pass a configuration like this. More on configuration options below.
var config = new QueryKitConfiguration(config =>
{
config.Property<SpecialPerson>(x => x.FirstName)
.HasQueryName("first")
.PreventSort();
});
var people = _dbContext.People
.ApplyQueryKitFilter("first == "Jane" && Age < 10", config)
.ToList();
When filtering, you can use logical operators && for and as well as || for or. For example:
var input = """FirstName == "Jane" && Age < 10""";
var input = """FirstName == "Jane" || FirstName == "John" """;Additionally, you can use ^^ for an in operator. You can add an * and use ^^* for case-insensitivity as well:
var input = """(Age ^^ [20, 30, 40]) && (BirthMonth ^^* ["January", "February", "March"]) || (Id ^^ ["6d623e92-d2cf-4496-a2df-f49fa77328ee"])""";You can use order of operation with parentheses like this:
var input = """(FirstName == "Jane" && Age < 10) || FirstName == "John" """;There's a wide variety of comparison operators that use the same base syntax as Sieve's operators. To do a case-insensitive operation, just append a * at the end of the operator.
| Name | Operator | Case Insensitive Operator |
|---|---|---|
| Equals | == | ==* |
| Not Equals | != | != |
| Greater Than | > | N/A |
| Less Than | < | N/A |
| Greater Than Or Equal | >= | N/A |
| Less Than Or Equal | <= | N/A |
| Starts With | _= | _=* |
| Does Not Start With | !_= | !_=* |
| Ends With | _-= | _-=* |
| Does Not End With | !_-= | !_-=* |
| Contains | @= | @=* |
| Does Not Contain | !@= | !@=* |
string and guid properties should be wrapped in double quotes
null doesn't need quotes: var input = "Title == null";
Double quotes can be escaped by using a similar syntax to raw-string literals introduced in C#11:
var input = """""Title == """lamb is great on a "gee-ro" not a "gy-ro" sandwich""" """"";
// OR
var input = """""""""Title == """"lamb is great on a "gee-ro" not a "gy-ro" sandwich"""" """"""""";
Dates and times use ISO format:
DateOnly: var filterInput = "Birthday == 2022-07-01";
DateTimeOffset:
var filterInput = "Birthday == 2022-07-01T00:00:03Z";DateTime: var filterInput = "Birthday == 2022-07-01";
var filterInput = "Birthday == 2022-07-01T00:00:03";var filterInput = "Birthday == 2022-07-01T00:00:03+01:00";TimeOnly: var filterInput = "Time == 12:30:00";
bool properties need to use == true, == false, or the same using the != operator. they can not be standalone properies:
var input = """Title == "chicken & waffles" && Favorite""";var input = """Title == "chicken & waffles" && Favorite == true""";var input = """(Title == "lamb" && ((Age >= 25 && Rating < 4.5) || (SpecificDate <= 2022-07-01T00:00:03Z && Time == 00:00:03)) && (Favorite == true || Email.Value _= "hello@gmail.com"))""";Filtering is set up to create an expression using the property names you have on your entity, but you can pass in a config to customize things a bit when needed.
HasQueryName() to create a custom alias for a property. For exmaple, we can make FirstName aliased to first.PreventFilter() to prevent filtering on a given propertyvar input = $"""first == "Jane" || Age > 10""";
var config = new QueryKitConfiguration(config =>
{
config.Property<SpecialPerson>(x => x.FirstName)
.HasQueryName("first");
config.Property<SpecialPerson>(x => x.Age)
.PreventFilter();
});Say we have a nested object like this:
public class SpecialPerson
{
public Guid Id { get; set; } = Guid.NewGuid();
public EmailAddress Email { get; set; }
}
public class EmailAddress : ValueObject
{
public EmailAddress(string? value)
{
Value = value;
}
public string? Value { get; private set; }
}To actually use the nested properties, you can do something like this:
var input = $"""Email.Value == "{value}" """;
// or with an alias...
var input = $"""email == "hello@gmail.com" """;
var config = new QueryKitConfiguration(config =>
{
config.Property<SpecialPerson>(x => x.Email.Value).HasQueryName("email");
});Note, with EF core, your config might look like this:
public sealed class PersonConfiguration : IEntityTypeConfiguration<SpecialPerson>
{
public void Configure(EntityTypeBuilder<SpecialPerson> builder)
{
builder.HasKey(x => x.Id);
// Option 1
builder.Property(x => x.Email)
.HasConversion(x => x.Value, x => new EmailAddress(x))
.HasColumnName("email");
// Option 2
builder.OwnsOne(x => x.Email, opts =>
{
opts.Property(x => x.Value).HasColumnName("email");
}).Navigation(x => x.Email)
.IsRequired();
}
}Warning EF properties configured with
HasConversionare not supported at this time -- if this is a blocker for you, i'd love to hear your use case
Sorting is a more simplistic flow. It's just an input with a comma delimited list of properties to sort by.
asc or desc to designate if you want it to be ascending or descending. If neither is used, QueryKit will assume asc- to designate it as descSo all of these are valid:
var input = "Title";
var input = "Title, Age desc";
var input = "Title desc, Age desc";
var input = "Title, Age";
var input = "Title asc, -Age";
var input = "Title, -Age";Sorting is set up to create an expression using the property names you have on your entity, but you can pass in a config to customize things a bit when needed.
HasQueryName() to create a custom alias for a property. For exmaple, we can make FirstName aliased to first.PreventSort() to prevent filtering on a given propertyvar input = $"""Age desc, first"";
var config = new QueryKitConfiguration(config =>
{
config.Property<SpecialPerson>(x => x.FirstName)
.HasQueryName("first")
.PreventSort();
});