DynamicQueryable.Extensions 1.9.0

Suggested Alternatives

FlexQuery.NET

Additional Details

This package has been deprecated. Please use FlexQuery.NET instead:
FlexQuery.NET

There is a newer version of this package available.
See the version list below for details.
dotnet add package DynamicQueryable.Extensions --version 1.9.0
                    
NuGet\Install-Package DynamicQueryable.Extensions -Version 1.9.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="DynamicQueryable.Extensions" Version="1.9.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="DynamicQueryable.Extensions" Version="1.9.0" />
                    
Directory.Packages.props
<PackageReference Include="DynamicQueryable.Extensions" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add DynamicQueryable.Extensions --version 1.9.0
                    
#r "nuget: DynamicQueryable.Extensions, 1.9.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package DynamicQueryable.Extensions@1.9.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=DynamicQueryable.Extensions&version=1.9.0
                    
Install as a Cake Addin
#tool nuget:?package=DynamicQueryable.Extensions&version=1.9.0
                    
Install as a Cake Tool

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 multiple query-string 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, collection paths (EXISTS/Any)
  • Sorting: multi-field ordering
  • Paging: page / pageSize or skip / take (format-dependent)
  • Projection: select with nested properties, plus include-style expansion
  • Query formats: Generic, JSON, DSL, JQL-lite, Syncfusion, Laravel Spatie
  • 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(), and avg() 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 (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

Supported JQL operators:

  • = != > >= < <=
  • CONTAINS
  • IN (...) and NOT IN (...)

Unlike DSL/JSON malformed-input handling, invalid JQL syntax is surfaced as a parse exception to callers.

Syncfusion

?where[0][field]=Name
&where[0][operator]=contains
&where[0][value]=john
&sorted[0][name]=Age
&sorted[0][direction]=descending
&skip=0
&take=10

Use condition=and|or for top-level logic.

Laravel Spatie

Implicit AND (default Spatie behavior)

?filter[name]=Alice Johnson
&filter[status]=Active

Nested grouping

?filter[or][0][name]=john
&filter[or][1][name]=doe

Explicit operator support (extension)

?filter[name][operator]=contains
&filter[name][value]=john

(Works inside nested and/or groups as well.)

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")

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
  • %valueEndsWith
  • value%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 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. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.