Dekiru.QueryFilter
9.4.6
Prefix Reserved
dotnet add package Dekiru.QueryFilter --version 9.4.6
NuGet\Install-Package Dekiru.QueryFilter -Version 9.4.6
<PackageReference Include="Dekiru.QueryFilter" Version="9.4.6" />
paket add Dekiru.QueryFilter --version 9.4.6
#r "nuget: Dekiru.QueryFilter, 9.4.6"
// Install Dekiru.QueryFilter as a Cake Addin #addin nuget:?package=Dekiru.QueryFilter&version=9.4.6 // Install Dekiru.QueryFilter as a Cake Tool #tool nuget:?package=Dekiru.QueryFilter&version=9.4.6
Dynamic Query Library
This library provides a parser for a simple query language that can be used to filter data in the form of IQueryable<T>. The main use case is to provide a simple query language for REST APIs to use with Entity Framework Core. The library is inspired by the OData query language, but only a subset of the OData query language is supported (i.e., $filter
, $orderby
, $select
, and $expand
).
Note on versioning. Previously, this library was released with multi-targeting, where the first digit of the version number indicated the target framework. Starting with 9.4.0, this is no longer the case. The library will only target the lowest still supported .NET version (at the time of writing, .NET 8). The library will move onto v10.x.y on November 10, 2026, when .NET 8 goes out of support.
The library is available as a NuGet package: Dekiru.QueryFilter.
The library is primarily intended to be used with Entity Framework Core, but it can be used with any data source that supports IQueryable<T>
(Excluding Include-methods, as they are tightly coupled with Entity Framework Core). There is no explicit dependency on Entity Framework Core, the library will attempt to dynamically load the Microsoft.EntityFrameworkCore
assembly when it is needed, which is when IncludeDynamic
is called.
Should this load fail, the library will throw an exception. To avoid this, the Microsoft.EntityFrameworkCore
assembly can be loaded explicitly by calling QueryFilterParser.RegisterEntityFrameworkAssembly(Assembly)
and passing the assembly that contains the Microsoft.EntityFrameworkCore
namespace.
The following methods are available:
Count<T>(string? filter)
- Returns the number of entries in the collection that matches the filter. The filter is optional, and if it is not supplied, the method will return the total number of entries in the collection. If supplied, it follows the same syntax asFilterDynamic
.SelectDynamic<Dictionary<string, object?>>(string[] columns)
- Projects the collection to a new collection of an anonymous type with the specified columns.SelectDynamic<TSource, TResult>(string[] columns)
- Projects the collection to a new collection of the specified type with the specified columns.FilterDynamic<T>(string filter)
- Filters the collection using the specified filter.IncludeDynamic<T>(string include)
- Includes the specified navigation properties in the collection.IncludeDynamic<T>(string[] includes)
- Wrapper for including multiple properties viaIncludeDynamic(string include)
.
SortDynamic<T>(string orderBy)
- Orders the collection by the specified order.RemoveCyclesAsync<T>()
- Removes cycles from the collection. This is useful when serializing the collection to JSON. It does this by extracting which properties have been explicitly included in the query, and sets the rest to null. This method will enumerate the collection (thus executing the SQL query), and should be used as late as possible in the query pipeline.RemoveCycles<T>()
- Synchronous version ofRemoveCyclesAsync<T>()
.RemoveCyclesFirstOrDefaultAsync<T>()
- Calls FirstOrDefaultAsync instead of ToListAsync.RemoveCyclesFirstOrDefault<T>()
- Synchronous version ofRemoveCyclesFirstOrDefaultAsync<T>()
.
Generally, any C# property or method (whether static or instance) that belong to a supported type can be used in the query language. This, however, excludes extension methods such as Any
or Count
. Some extension methods are still supported, see more about this in the Functions section.
The only limiting factor is whether or not Entity Framework Core can translate the expression to SQL. Entity Framework Core will throw an exception if it encounters an expression that cannot be translated to SQL.
If you encounter such an exception, you may need to rewrite the query to avoid the offending expression. If encounter an exception that you believe should be translatable to SQL, please open an issue.
There is a special meta variable called
$
, that can be used to refer to the Entity Framework CoreDbContext
instance. This can be used to filter on entities that are not connected to the entity that is being queried.
Note: Null semantics, as well as case sensitivity, are determined by the underlying data source.
Filtering
Filtering is done via a simplified query langauge, which is inspired by the OData query language. See below for a list of supported operators and functions. It is possible to filter on properties of the entity, as well as on navigation properties and their properties, at any depth.
Note: Filtering on navigation properties will not affect any included properties, it will only affect the root collection. To filter included properties, the filter must be applied in the
IncludeDynamic
call.
Macros
Macros may be registered by calling any of the FilterMacros.Add
methods. Macros are used to define reusable expressions that can be used in queries, and can be defined for any type. Macros are defined in the form of a lambda expression, whose body will be inlined into the query, and can be used to encapsulate complex expressions as well as exposing extension methods as defined on EF.Functions
.
Macros may have 0 to 5 arguments, which may be of any type.
When used in a filter expression, macros are referenced by their name, and can be called with arguments. Arguments are passed in the form of a lambda expression, and can be used in the macro body. They are invoked by prefixing the macro name with a @
, like so: @myMacro
, @myMacro()
, or @myMacro(arg1, arg2)
.
Because the "current" entity is implicit, there are some exceptions when passing a reference to a macro. Given the following macro:
FilterMacros.Add<MyEntity, int>("myMacro", (e, id) => e.Id == id)
A filter expression using this macro on the MyEntity
entity would look like this:
@myMacro(42)
or @myMacro(this, 42)
While resolving a macro, if the library fails to find a macro with the name and parameters specified (i.e. @myMacro(42)
), it will insert the current entity at the head of the argument list and try again (i.e. as if call had been @myMacro(this, 42)
). Because of the implicit this
argument, the macro can be called with just the argument, or with the this
argument explicitly passed. As such, a macro defined as FilterMacros.Add<MyEntity>("notDeleted", e => !e.Deleted)
can be called as @notDeleted
or @notDeleted(this)
. Parentheses are required when passing additional arguments to the macro.
It is also permissible to call a macro without using the @
prefix, but in that case parentheses are required and the argument types must match the macro definition exactly, as due to technical limitations the library cannot infer the types of the arguments.
Macros can also be used while filtering on navigation properties. In this case, the macro must be defined for the type of the navigation property:
myEntities.any(e: @myMacro(e, 42))
Here's how one might define a macro to wrap the EF.Functions.Like
method:
FilterMacros.Add<string, string, bool>("like", (s, pattern) => EF.Functions.Like(s, pattern))
And here's how one might use it in a filter expression:
@like(Name, "John%")
Including navigation properties
Navigation properties can be included in the query by using the IncludeDynamic
method. It matches the feature set of Include
and ThenInclude
in Entity Framework Core.
The include syntax has the following variants:
MyProperty
- Includes the navigation propertyMyProperty
.MyProperty.SubProperty
- Includes the navigation propertyMyProperty
, and then includes the navigation propertySubProperty
.MyProperty1,MyProperty2
- Includes the navigation propertiesMyProperty1
andMyProperty2
.
Each variant can be combined with the others. If the included property is a collection, a filter expression can be used to filter the included values. This is done by appending the filter to the property like so: MyProperty(Name eq 'Foo')
. Note that the filter at the root level does not use a lambda expression, as it is implied. Nested filters will require a lambda expression, however.
Note: Applying a filter to an included collection will not affect the root collection, it will only affect the included values. To filter the root collection, the filter must be applied in the
FilterDynamic
call.
Beyond filtering, it is also possible to sort and paginate the included properties. This is done by appending sort, skip, and/or take to the property like so: MyProperty:sort(Name desc):skip(10):take(5)
. The sort, skip, and take expressions can be combined in any order, but the sort expression will always be applied first.
It is possible to sort on multiple properties by repeating the sort expression: MyProperty:sort(Name desc):sort(Age asc)
. The sort expressions will be applied in the order they are specified. Specifying the sort direction is optional, and if it is not supplied, the default sort direction will be ascending.
There are some configurable limits to the depth of the included properties, as well as the number of items that can be included. These limits can be configured by calling the SetMaxIncludeDepth
and SetMaxIncludeItems
methods on the QueryFilterExtensions
class. The default values are no limit on the depth or number of items.
Limiting which properties can be included, sorted, or filtered
The following attributes are available to limit which properties can be included, sorted, or filtered:
[DisableInclude]
- Disables including the property.[DisableSort]
- Disables sorting on the property.[DisableFilter]
- Disables filtering on the property.
All attributes can currently only be applied to properties. Making [DisableInclude]
work on classes is a planned feature.
Operators
The following infix/prefix operators are supported:
and
- SQLAND
operatoror
- SQLOR
operatornot
- SQLNOT
operatoreq
- SQL=
operatorne
- SQL<>
operatorgt
- SQL>
operatorge
- SQL>=
operatorlt
- SQL<
operatorle
- SQL<=
operatorin
- SQLIN
operator
Function calls
All methods that work on properties can be called either as a static method or as a member method. The syntax is either myMethod(SomeCollection)
or SomeCollection.myMethod()
. There is no functional difference between the two syntaxes, it is just a matter of preference.
Some methods may be called with a predicate, they are defined as a lamba expression with the following syntax:
SomeCollection.myMethod(c: c.Name eq 'John')
Nested predicates are supported:
SomeCollection.any(c: c.OtherProp.Count(r: r.Name eq 'Bar') gt 5)
Functions
Aside from the methods as described above, the following extension methods are available:
- Collection
Count(identifier)
- Returns the number of entries in the collection:Items.count()
Count(identifier, predicate)
- Returns the number of entries in the collection that matches the predicate:Items.count(i: i.Name eq "Foo")
- In addition to the normal count methods, CountLong also exists with the same overloads, returning a long instead of an int.
Sum(identifier, selector)
- Returns the sum of a property in all entries in the collection:Items.sum(i: i.SomeNumber)
Max(identifier, selector)
- Returns the maximum value of a property in the collection:Items.max(i: i.SomeNumber)
Any(identifier)
- Returns true if the collection contains any entries:Items.any()
Any(identifier, predicate)
- Returns true if the collection contains any entries that match the predicate:Items.any(i: i.Name eq "Foo")
All(identifier, predicate)
- Returns true if all of the entries in the collection matches the predicate:Items.all(i: i.Name eq "Foo")
- Conversion - Normally, the library attempts to handle conversions automatically. However, in some cases it may be necessary to explicitly convert a value. The following conversion methods are available:
Long(value)
- Converts a value to a long:SomeProp.long()
ULong(value)
- Converts a value to an unsigned long:SomeProp.ulong()
Int(value)
- Converts a value to an int:SomeProp.int()
UInt(value)
- Converts a value to an int:SomeProp.uint()
Short(value)
- Converts a value to a short:SomeProp.short()
UShort(value)
- Converts a value to an ushort:SomeProp.ushort()
Byte(value)
- Converts a value to a byte:SomeProp.byte()
SByte(value)
- Converts a value to an sbyte:SomeProp.sbyte()
Decimal(value)
- Converts a value to a decimal:SomeProp.decimal()
Double(value)
- Converts a value to an double:SomeProp.double()
Float(value)
- Converts a value to an float:SomeProp.float()
DateOnly(value)
- Converts a value to a DateOnly:SomeProp.dateOnly()
TimeOnly(value)
- Converts a value to a TimeOnly:SomeProp.timeOnly()
DateTimeOffset(value)
- Converts a value to a DateTimeOffset:SomeProp.dateTimeOffset()
DateTime(value)
- Converts a value to a DateTime:SomeProp.dateTime()
TimeSpan(value)
- Converts a value to a TimeSpan:SomeProp.timeSpan()
Guid(value)
- Converts a value to a Guid:SomeProp.guid()
String(value)
- Converts a value to a string:SomeProp.string()
- Date
Now()
- Returns a DateTime object representing the current date and time:now()
UtcNow()
- Returns a DateTime object representing the current date and time in UTC:utcNow()
Today()
- Returns a DateTime object representing the current date:today()
Ago(span)
- Returns a DateTime object with the specified ISO 8601 duration subtracted from current date and time:since("PT1D")
AgoUtc(span)
- Returns a DateTime object with the specified ISO 8601 duration subtracted from current date and time in UTC:sinceUtc("PT1D")
- Object
Between(value, fromValue, toValue)
- Returns true if the value is equal to, or between, two other values. Works on any type that supports gte and lte:SomeNumber.between(1, 10)
Coalesce(value1, value2)
- Returns the value of the property if it is not null, otherwise the supplied value will be returned:Name.coalesce("bar")
IsNull(identifier)
- Returns true if the property is null:Name.isnull()
IsNotNull(identifier)
- Returns true if the property is not null:Name.isnotnull()
- String
Lower(identifier)
- Converts a string to lower case:Name.lower()
(This is equivalent toName.ToLower()
, and is only included to support existing queries that use this syntax.)Upper(value)
- Converts a string to upper case:Name.upper()
NotContains(identifier, arg)
- Negated Contains:name.NotContains("foo")
Empty(identifier)
- Returns true if the property is an empty string:Name.empty()
Function names are case insensitive.
Sample queries
Query | LINQ equivalent |
---|---|
Name eq 'John' and not LastName eq 'Doe' |
n => n.Name == "John" && n.LastName != "Doe" |
Name eq 'John' and Age gt 30 |
n => n.Name == "John" && n.Age > 30 |
Name eq 'John' or Age gt 30 |
n => n.Name == "John" || n.Age > 30 |
Name eq 'John' and (Age gt 30 or Age lt 20) |
n => n.Name == "John" && (n.Age > 30 || n.Age < 20) |
Foo * Bar eq 42 / Baz |
n => n.Foo * n.Bar == 42 / n.Baz |
Children.any() |
n => n.Children.Any() |
Children.any(c: c.Name eq 'Bar') |
n => n.Children.Any(c => c.Name == "Bar") |
Children.count() gt 0 |
n => n.Children.Count() > 0 |
Children.count(c: c.Name eq 'Bar') gt 0 |
n => n.Children.Count(c => c.Name == "Bar") > 0 |
Children.all(c: c.Name eq 'Bar') |
n => n.Children.All(c => c.Name == "Bar") |
Children.count(c: c.Relatives.any(r: r.Name eq 'Bar')) gt 0 |
n => n.Children.Count(c => c.Relatives.Any(r => r.Name == "Bar")) > 0 |
Children.any(c: $.OtherEntity.Any(o: o.Name eq c.Name)) |
n => n.Children.Any(c => myDbContext.OtherEntity.Any(o => o.Name == c.Name)) |
Address.Country eq 'Sweden' |
n => n.Address.Country == "Sweden" |
Name.startswith('J') |
n => n.Name.StartsWith("J") |
Name.endswith('n') |
n => n.Name.EndsWith("n") |
Foo in (1, 2, 3) |
n => new[] { 1, 2, 3 }.Contains(n.Foo) |
Order of operations
The order of operations is as follows, from highest to lowest precedence:
- Identifiers, literals, array expressions, and parentheses
- Member access, and function calls
IN
operatorNOT
and unary minus operator- Multiplicative operators (
*
,/
,%
) - Additive operators (
+
,-
) - Relative operators (
lt
,lte
,gt
,gte
) - Equality operators (
eq
,ne
) - Logical
AND
operator - Logical
OR
operator - Lambda expressions
Product | Versions Compatible and additional computed target framework versions. |
---|---|
.NET | net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 was computed. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. |
-
net8.0
- No dependencies.
NuGet packages (1)
Showing the top 1 NuGet packages that depend on Dekiru.QueryFilter:
Package | Downloads |
---|---|
Dekiru.Internals.ApiUtils
Package Description |
GitHub repositories
This package is not used by any popular GitHub repositories.
Version | Downloads | Last updated |
---|---|---|
9.4.6 | 83 | 1/24/2025 |
9.4.5 | 79 | 1/23/2025 |
9.4.4 | 261 | 12/13/2024 |
9.4.3 | 118 | 12/5/2024 |
9.4.2 | 104 | 12/2/2024 |
9.4.1 | 138 | 11/18/2024 |
9.4.0 | 96 | 11/15/2024 |
9.3.2 | 104 | 11/13/2024 |
9.3.1 | 98 | 11/13/2024 |
8.3.2 | 120 | 11/13/2024 |
8.3.1 | 95 | 11/13/2024 |
8.3.0 | 125 | 11/1/2024 |
8.2.0 | 144 | 10/10/2024 |
8.1.12 | 139 | 10/3/2024 |
8.1.11 | 113 | 10/1/2024 |
8.1.10 | 101 | 10/1/2024 |
8.1.9 | 198 | 9/17/2024 |
8.1.8 | 123 | 9/17/2024 |
8.1.7 | 133 | 9/3/2024 |
8.1.6 | 121 | 8/30/2024 |
8.1.5 | 498 | 8/13/2024 |
6.1.12 | 99 | 10/3/2024 |
6.1.11 | 96 | 10/1/2024 |
6.1.10 | 96 | 10/1/2024 |
6.1.9 | 136 | 9/17/2024 |
6.1.8 | 118 | 9/17/2024 |
6.1.7 | 107 | 9/3/2024 |
6.1.6 | 105 | 8/30/2024 |
6.1.5 | 136 | 8/13/2024 |
6.1.4 | 117 | 8/13/2024 |
3.1.4 | 87 | 8/3/2024 |
3.1.3 | 195 | 7/1/2024 |
3.1.2 | 106 | 7/1/2024 |
3.1.1 | 116 | 7/1/2024 |
3.1.0 | 138 | 6/28/2024 |
3.0.6 | 138 | 6/14/2024 |
3.0.4 | 164 | 5/23/2024 |
3.0.3 | 129 | 5/10/2024 |
3.0.2 | 100 | 5/3/2024 |
3.0.1 | 143 | 4/30/2024 |
3.0.0 | 132 | 4/26/2024 |
2.6.5 | 121 | 4/25/2024 |
2.6.4 | 130 | 4/25/2024 |
2.6.3 | 122 | 4/24/2024 |
2.6.2 | 124 | 4/22/2024 |
2.6.1 | 158 | 4/16/2024 |
2.6.0 | 131 | 4/15/2024 |
2.5.4 | 148 | 3/29/2024 |
2.5.3 | 123 | 3/28/2024 |
2.5.2 | 127 | 3/27/2024 |
2.5.1 | 131 | 3/27/2024 |
2.5.0 | 124 | 3/20/2024 |
2.4.0 | 128 | 3/15/2024 |
2.3.0 | 117 | 2/29/2024 |
2.2.1 | 129 | 2/21/2024 |
2.1.0 | 144 | 1/29/2024 |
2.0.9 | 133 | 1/17/2024 |
2.0.8 | 125 | 1/16/2024 |
2.0.5 | 128 | 1/12/2024 |
2.0.3 | 175 | 12/20/2023 |
2.0.2 | 127 | 12/20/2023 |
2.0.1 | 138 | 12/19/2023 |
2.0.0 | 144 | 12/19/2023 |
1.0.0 | 200 | 3/4/2023 |