DynamicQueryable.Extensions
2.2.0
This package has been deprecated. Please use FlexQuery.NET instead:
FlexQuery.NET
See the version list below for details.
dotnet add package DynamicQueryable.Extensions --version 2.2.0
NuGet\Install-Package DynamicQueryable.Extensions -Version 2.2.0
<PackageReference Include="DynamicQueryable.Extensions" Version="2.2.0" />
<PackageVersion Include="DynamicQueryable.Extensions" Version="2.2.0" />
<PackageReference Include="DynamicQueryable.Extensions" />
paket add DynamicQueryable.Extensions --version 2.2.0
#r "nuget: DynamicQueryable.Extensions, 2.2.0"
#:package DynamicQueryable.Extensions@2.2.0
#addin nuget:?package=DynamicQueryable.Extensions&version=2.2.0
#tool nuget:?package=DynamicQueryable.Extensions&version=2.2.0
DynamicQueryable
DynamicQueryable is a lightweight .NET library for applying dynamic filtering, sorting, paging, and projection to IQueryable (EF Core or any LINQ provider). It supports a focused set of query formats and produces EF Core-translatable expression trees.
Installation
dotnet add package DynamicQueryable.Extensions
Optional (async helpers for EF Core):
dotnet add package DynamicQueryable.Extensions.EFCore
Quick Start
Parse request query into QueryOptions
using DynamicQueryable.Parsers;
var options = QueryOptionsParser.Parse(Request.Query);
Apply to IQueryable
using DynamicQueryable.Extensions;
using Microsoft.EntityFrameworkCore;
[HttpGet]
public async Task<IActionResult> Get()
{
var options = QueryOptionsParser.Parse(Request.Query);
// Filter + sort + paging
var users = await _context.Users
.ApplyQueryOptions(options)
.ToListAsync();
// Projection (optional)
var projected = await _context.Users
.ApplyQueryOptions(options)
.ApplySelect(options)
.ToListAsync();
return Ok(new { users, projected });
}
Features
- Filtering: nested AND/OR groups, nested property paths, scoped collection filtering (
.any(),.all(),[...]) - Sorting: multi-field ordering
- Paging:
page/pageSizeorskip/take(format-dependent) - Projection:
selectwith nested properties, plusinclude-style expansion - Query formats: DSL (primary), JSON (advanced), Indexed (compatibility), JQL fallback
- EF Core friendly: expression-tree based, provider-translatable
- Pluggable operators: core ships framework-agnostic handlers, optional packages can override by operator
🔽 Sorting
- Basic:
?sort=createdAt:desc - Multi-field:
?sort=createdAt:desc,total:asc - Nested:
?sort=customer.name:asc - Aggregate:
?sort=orders.sum(total):desc,orders.count():asc
- Default direction is
asc. - Dot notation is supported for nested properties.
- Aggregate Functions: Supports
sum(),count(),max(),min(), andavg()on collection paths. - Direct Collection Sorting: Sorting directly on a collection property (e.g.,
orders.total) is NOT supported; use an aggregate instead.
Filtering & Query Formats
DynamicQueryable parses incoming query parameters into a unified model (QueryOptions, FilterGroup, FilterCondition). Operator behavior is consistent across formats.
Generic (indexed)
Simple filter
?filter[0].field=Name
&filter[0].operator=contains
&filter[0].value=john
Top-level logic (AND/OR)
?logic=or
&filter[0].field=City&filter[0].operator=eq&filter[0].value=Berlin
&filter[1].field=City&filter[1].operator=eq&filter[1].value=Paris
JSON (nested groups)
Advanced nested logic
?filter={
"logic":"and",
"filters":[
{
"logic":"or",
"filters":[
{"field":"City","operator":"eq","value":"London"},
{"field":"City","operator":"eq","value":"Berlin"}
]
},
{"field":"Age","operator":"between","value":"25,40"}
]
}
DSL (compact filter string)
Use & for AND (URL-encode as %26), | for OR, parentheses for grouping:
?filter=((city:eq:London|city:eq:Berlin)%26(age:between:25,40|status:eq:Pending))
DSL advanced operators:
?filter=!name:eq:john
?filter=not(name:eq:john)
?filter=name:like:%john%
?filter=orders:any:total:gt:100
?filter=orders:count:gt:5
Grouping, aggregates, and having
Use group, aggregate functions in select, and having for post-aggregation filtering:
?group=category,status
&select=category,sum(total),count(id)
&having=sum(total):gt:10000
Supported aggregate functions in select/having:
sum(field)count(field)avg(field)
JQL-lite fallback (query)
Use SQL-like operators with AND / OR and parentheses for grouping:
?query=(name = "john" OR name = "doe") AND age >= 20
Supports nested property paths and quoted values:
?query=email = "ops@acmeretail.com" AND orders.number = "ORD-2026-0002" AND orders.items.quantity > 2
Scoped collection filtering (conditions apply to the same element):
?query=orders.any(status = Cancelled AND total > 500)
?query=orders[status = Cancelled AND orderItems.any(id = 101)]
Supported JQL operators:
=!=>>=<<=CONTAINSIN (...)andNOT IN (...)IS NULLandIS NOT NULLBETWEEN ... AND ...LIKE,STARTSWITH,ENDSWITH- Collection predicates:
ANY,ALL,COUNT
Unlike DSL/JSON malformed-input handling, invalid JQL syntax is surfaced as a parse exception to callers.
Migration Notes (v2.0.0)
- Removed legacy format adapters: Spatie and Syncfusion
- Removed Syncfusion-style sorting support
- Supported formats are now DSL, JSON, Indexed, and JQL fallback
- If you were using Spatie/Syncfusion query strings, migrate requests to DSL or Indexed format
Operators
| Operator | Description | Example |
|---|---|---|
eq |
Equal | Name eq 'John' |
neq |
Not equal | Age neq 30 |
gt |
Greater than | Age gt 18 |
gte |
Greater than or equal | Age gte 18 |
lt |
Less than | Age lt 60 |
lte |
Less than or equal | Age lte 60 |
contains |
String contains | Name contains 'jo' |
startswith |
String starts with | Name startswith 'Jo' |
endswith |
String ends with | Name endswith 'hn' |
in |
Value exists in a list | Status in ['Active','Pending'] |
notin |
Value does not exist in list | Status notin ['Inactive'] |
between |
Inclusive range | Age between 18,60 |
isnull |
Is null | DeletedAt isnull |
notnull |
Is not null | DeletedAt notnull |
like |
SQL LIKE pattern | Name like %john% |
any |
Collection element predicate | Orders any Total gt 100 |
count |
Collection count compare | Orders count gt 5 |
! / not() |
Negates condition/group | !Name eq John |
Nested & Collections
Nested property paths
Dot-notation works across filtering and projection:
?filter[0].field=Profile.Bio&filter[0].operator=contains&filter[0].value=dev
&select=Id,Profile.Bio
Collection paths (parent filtering)
Filtering on a collection navigation (e.g. Orders.Number) uses Any(...) / EXISTS semantics for the parent:
?filter[0].field=Orders.Number
&filter[0].operator=eq
&filter[0].value=SO-001
Conceptually:
x => x.Orders.Any(o => o.Number == "SO-001")
Scoped Collection Filtering (JQL)
By default, independent conditions on a collection are interpreted as separate Any() checks. Scoped filtering ensures multiple conditions apply to the same element.
| Syntax | Description |
|---|---|
orders.any(...) |
Conditions apply to the same order |
orders.all(...) |
All orders must satisfy the inner filter |
orders[...] |
Shorthand for orders.any(...) |
Conceptually:
?query=orders.any(status = Cancelled AND total > 500)
Translates to:
x => x.Orders.Any(o => o.Status == "Cancelled" && o.Total > 500)
Nested Scoped Filtering:
?query=orders.any(status = Cancelled AND orderItems.any(id = 101))
This ensures the orderItems condition is checked against items belonging to a Cancelled order. Scoped filters can be nested recursively to any depth.
Filtered child collections (when selected)
When a request filters on a collection path and that collection is also projected, the returned child collection is filtered to match the same criteria (projection-based, EF Core translatable):
?filter[0].field=Orders.Number&filter[0].operator=eq&filter[0].value=SO-001
&select=Id,Orders.Number
Conceptually:
x => new {
x.Id,
Orders = x.Orders
.Where(o => o.Number == "SO-001")
.Select(o => new { o.Number })
.ToList()
}
API Methods
Apply filter/sort/paging
using DynamicQueryable.Extensions;
var options = QueryOptionsParser.Parse(Request.Query);
var query = _context.Users.AsQueryable()
.ApplyQueryOptions(options); // filter + sort + paging
var data = await query.ToListAsync();
Apply projection (select / include / JSON select tree)
var projected = await _context.Users
.ApplyQueryOptions(options)
.ApplySelect(options)
.ToListAsync(); // IQueryable<object>
Return results with metadata
var result = _context.Users.ToQueryResult(options);
// result.Data, result.TotalCount, result.Page, result.PageSize
Return projected results with metadata
var result = _context.Users.ToProjectedQueryResult(options);
// result.Data is List<object> shaped by Select/Includes/JSON select tree
EF Core async helpers (package: DynamicQueryable.Extensions.EFCore)
var result = await _context.Users.ToQueryResultAsync(options, cancellationToken);
var projected = await _context.Users.ToProjectedQueryResultAsync(options, cancellationToken);
EF Core operator overrides (optional)
Core does not depend on EF Core. By default, like is handled with a framework-agnostic fallback:
%value%→Contains%value→EndsWithvalue%→StartsWith
When using the EF Core package, opt in to EF-specific operator handlers:
using DynamicQueryable.Extensions.EFCore;
var options = QueryOptionsParser.Parse(Request.Query)
.UseEfCoreOperators(); // registers EF.Functions.Like handler
var data = await _context.Users
.ApplyQueryOptions(options)
.ToListAsync();
ASP.NET Integration (optional)
You can parse directly in controllers/minimal APIs:
public abstract class BaseController : ControllerBase
{
protected QueryOptions Options => QueryOptionsParser.Parse(Request.Query);
}
License
MIT License
| 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. net10.0 was computed. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net8.0
- Microsoft.Extensions.Primitives (>= 8.0.0)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated | |
|---|---|---|---|
| 2.5.0 | 173 | 5/1/2026 | |
| 2.4.0 | 114 | 5/1/2026 | |
| 2.3.0 | 110 | 5/1/2026 | |
| 2.2.1 | 107 | 5/1/2026 | |
| 2.2.0 | 110 | 5/1/2026 | |
| 2.1.0 | 104 | 4/30/2026 | |
| 2.0.1 | 111 | 4/30/2026 | |
| 2.0.0 | 107 | 4/30/2026 | |
| 1.9.0 | 111 | 4/30/2026 | |
| 1.8.0 | 139 | 4/30/2026 | |
| 1.7.0 | 110 | 4/29/2026 | |
| 1.6.4 | 117 | 4/29/2026 | |
| 1.6.2 | 107 | 4/29/2026 | |
| 1.6.1 | 102 | 4/29/2026 | |
| 1.6.0 | 111 | 4/29/2026 | |
| 1.5.0 | 114 | 4/29/2026 |