NetToolsKit.DynamicQuery
8.0.0-preview.1
dotnet add package NetToolsKit.DynamicQuery --version 8.0.0-preview.1
NuGet\Install-Package NetToolsKit.DynamicQuery -Version 8.0.0-preview.1
<PackageReference Include="NetToolsKit.DynamicQuery" Version="8.0.0-preview.1" />
<PackageVersion Include="NetToolsKit.DynamicQuery" Version="8.0.0-preview.1" />
<PackageReference Include="NetToolsKit.DynamicQuery" />
paket add NetToolsKit.DynamicQuery --version 8.0.0-preview.1
#r "nuget: NetToolsKit.DynamicQuery, 8.0.0-preview.1"
#:package NetToolsKit.DynamicQuery@8.0.0-preview.1
#addin nuget:?package=NetToolsKit.DynamicQuery&version=8.0.0-preview.1&prerelease
#tool nuget:?package=NetToolsKit.DynamicQuery&version=8.0.0-preview.1&prerelease
NetToolsKit.DynamicQuery
Lightweight utilities to build LINQ predicates and projections dynamically from structured inputs (filter trees, field lists, and simple search objects).
Introduction
This library provides a focused set of building blocks to generate:
- Expression<Func<TEntity, bool>> from logical filter trees (AND/OR, multiple operators)
- Expression<Func<TEntity, object>> for recursive projections (including nested collections)
- Practical IQueryable extensions for direct application in LINQ/EF Core
Features
- ✅ Logical filter trees with AND/OR, supporting Equal/NotEqual, Comparisons, In/NotIn, Between, Contains/StartsWith/EndsWith, and null/empty checks
- ✅ Null-safe navigation and nested collection filters with Any(...) along deep paths
- ✅ Dynamic projections to anonymous shapes from ProjectionField/ProjectionTree (nested objects and collections)
- ✅ IQueryable helpers: WhereDynamic and SelectDynamic (ProjectionField or string overloads)
- ✅ Request container with helpers: DynamicQueryContainer + HasPagination/HasProjection/HasWhereTreeFilter
- ✅ Optional object-search components (Composite/Leaf) to derive predicates from simple DTOs
- ✅ Guardrail limits for depth, node count, collection pressure, and projection breadth when handling untrusted input
Contents
- Introduction
- Features
- Installation
- Quick Start
- Usage Examples
- 1) WhereTreeFilter: AND/OR, In/Between, null-safe and nested Any
- 2) Dynamic projections: nested objects and collections
- 3) DynamicQueryContainer helpers (request wiring)
- 4) ObjectSearch (derive filters from a simple DTO)
- 5) WhereFilterInterpreter overview (from previous docs)
- 6) Dynamic request JSON with DynamicQueryContainer (from previous docs)
- 7) Guardrails for public or untrusted input
- API Reference
- References
- License
Installation
dotnet add package NetToolsKit.DynamicQuery
Via PackageReference:
<PackageReference Include="NetToolsKit.DynamicQuery" Version="*" />
Quick Start
using NetToolsKit.DynamicQuery;
using NetToolsKit.DynamicQuery.Projections;
using NetToolsKit.DynamicQuery.WhereFilters;
// Build a predicate from a filter tree
var filter = new WhereTreeFilter
{
OperatorType = WhereOperatorType.And,
Operands = new[]
{
new WhereTreeFilter { Property = "Name", FilterType = WhereFilterType.Contains, Value = "Silva" },
new WhereTreeFilter { Property = "Age", FilterType = WhereFilterType.GreaterThan, Value = 18 }
}
};
// Apply Where + Select dynamically
var people = dbContext.People
.WhereDynamic(filter)
.SelectDynamic(new[] { new ProjectionField("Id"), new ProjectionField("Name"), new ProjectionField("Address.City") })
.ToList();
Usage Examples
1) WhereTreeFilter: AND/OR, In/Between, null-safe and nested Any
using NetToolsKit.DynamicQuery;
using NetToolsKit.DynamicQuery.WhereFilters;
// (a) Simple AND
var andFilter = new WhereTreeFilter
{
OperatorType = WhereOperatorType.And,
Operands = new[]
{
new WhereTreeFilter { Property = "Status", FilterType = WhereFilterType.Equal, Value = "Active" },
new WhereTreeFilter { Property = "Age", FilterType = WhereFilterType.GreaterThanOrEqual, Value = 21 }
}
};
// (b) OR with In
var orFilter = new WhereTreeFilter
{
OperatorType = WhereOperatorType.Or,
Operands = new[]
{
new WhereTreeFilter { Property = "Role", FilterType = WhereFilterType.In, Value = new[] { "Admin", "Manager" } },
new WhereTreeFilter { Property = "City", FilterType = WhereFilterType.Contains, Value = "York" }
}
};
// (c) Between + null/empty checks
var betweenFilter = new WhereTreeFilter
{
OperatorType = WhereOperatorType.And,
Operands = new[]
{
new WhereTreeFilter { Property = "Score", FilterType = WhereFilterType.Between, Value = new[] { 70, 90 } },
new WhereTreeFilter { Property = "Tags", FilterType = WhereFilterType.IsNotNullOrEmpty }
}
};
// (d) Nested collection with Any + null-safe navigation ("?")
var nestedFilter = new WhereTreeFilter
{
OperatorType = WhereOperatorType.And,
Operands = new[]
{
// Will translate to: x => x.Orders.Any(o => o.ProductName.Contains("T-Shirt"))
new WhereTreeFilter { Property = "Orders.ProductName", FilterType = WhereFilterType.Contains, Value = "T-Shirt" },
// Null-safe navigation example (Address?.City)
new WhereTreeFilter { Property = "Address?.City", FilterType = WhereFilterType.Equal, Value = "Ipatinga" }
}
};
var result = dbContext.Users
.WhereDynamic(andFilter)
.WhereDynamic(orFilter)
.WhereDynamic(betweenFilter)
.WhereDynamic(nestedFilter)
.ToList();
2) Dynamic projections: nested objects and collections
using NetToolsKit.DynamicQuery;
using NetToolsKit.DynamicQuery.Projections;
// Using ProjectionField[]
var fields = new[]
{
new ProjectionField("Id"),
new ProjectionField("Name"),
new ProjectionField("Address.City"),
new ProjectionField("Orders.Id"),
new ProjectionField("Orders.Items.Sku")
};
var listA = dbContext.Users.SelectDynamic(fields).ToList();
// Using string[] overload
var listB = dbContext.Users.SelectDynamic(new[]
{
"Id", "Name", "Address.City", "Orders.Id", "Orders.Items.Sku"
}).ToList();
3) DynamicQueryContainer helpers (request wiring)
using NetToolsKit.DynamicQuery;
using NetToolsKit.DynamicQuery.Projections;
using NetToolsKit.DynamicQuery.WhereFilters;
var req = new DynamicQueryContainer
{
Pagination = new() { PageNumber = 1, PageSize = 10, OrderBy = "Name" },
Projection = new() { Projections = new[] { new ProjectionField("Id"), new ProjectionField("Name") } },
WhereFilter = new WhereTreeFilter { Property = "IsActive", FilterType = WhereFilterType.Equal, Value = true }
};
var q = dbContext.Users.AsQueryable();
if (req.HasWhereTreeFilter()) q = q.WhereDynamic(req.WhereFilter!);
if (req.HasProjection()) q = q.SelectDynamic(req.Projection!.Projections!);
// if (req.HasPagination()) apply Skip/Take/OrderBy (left to the caller)
var page = q.ToList();
4) ObjectSearch (derive filters from a simple DTO)
using NetToolsKit.DynamicQuery.ObjectSearch;
// Simple DTO filled by the client
var userSearch = new { Name = "John", Age = 30 };
// Composite component bound to the User entity (whitelisting properties)
var composite = new ObjectSearchComposite<dynamic>(
entityPropertyNames: new[] { "Name", "Age" },
entityType: typeof(User),
entitySearch: userSearch);
// Build expression: x => x.Name == "John" && x.Age == 30
var expr = composite.BuildExpression();
var users = dbContext.Users.Where(expr!).ToList();
Note: ObjectSearch is optional and intended for simple DTO-to-predicate scenarios. For complex logic and operators, prefer WhereTreeFilter.
7) Guardrails for public or untrusted input
using NetToolsKit.DynamicQuery;
var guardrails = new DynamicQueryGuardrailOptions
{
MaxWhereTreeDepth = 4,
MaxWhereNodeCount = 20,
MaxFilterValueCount = 25,
MaxProjectionFieldCount = 16,
MaxCollectionNavigationCount = 1
};
var page = dbContext.Users
.WhereDynamic(filter, guardrails)
.SelectDynamic(new[] { "Id", "Name", "Address.City" }, guardrails)
.ToList();
If a request exceeds the configured limits, the package throws DynamicQueryGuardrailException. For public HTTP endpoints, translate that exception into a 400 Bad Request with guidance for the caller.
5) WhereFilterInterpreter overview (from previous docs)
Transforms a WhereTreeFilter into a LINQ predicate Expression<Func<T, bool>> suitable for EF Core, LINQ to Objects, etc.
How it works
- Input: WhereTreeFilter with Property (e.g., "Address.City"), FilterType (Equal, Contains, Between, IsNull, ...), and Value.
- Processing: builds expression trees (AndAlso/OrElse), supports nested navigation (x.Address.City), converts values dynamically (enums, nullables, lists), and applies Any/Contains/Between where applicable, using type-safe conversions.
- Output: ready-to-use expression in .Where(...)
using NetToolsKit.DynamicQuery.WhereFilters;
var filter = new WhereTreeFilter
{
OperatorType = WhereOperatorType.And,
Operands = new[]
{
new WhereTreeFilter { Property = "Address.City", FilterType = WhereFilterType.Equal, Value = "Ipatinga" },
new WhereTreeFilter { Property = "Name", FilterType = WhereFilterType.Contains, Value = "Silva" }
}
};
var expr = WhereFilterInterpreter.BuildExpression<User>(filter);
var result = db.Users.Where(expr).ToList();
6) Dynamic request JSON with DynamicQueryContainer (from previous docs)
Build a dynamic request with pagination, whereFilter, and projection.
Base structure
{
"pagination": { },
"projection": { },
"whereFilter": { }
}
Pagination — page and sorting control
| Field | Description | Example |
|---|---|---|
orderBy |
Field for sorting (can be nested) | "Driver.Name" |
pageNumber |
Current page (starts at 1) | 1 |
pageSize |
Page size | 10 |
Example:
"pagination": {
"orderBy": "Driver.Name",
"pageNumber": 1,
"pageSize": 10
}
Projection — fields to return
Provide a list of objects with the field property (simple or nested paths):
| Field | Description | Example |
|---|---|---|
field |
Path of the field to be projected | "Carrier.Name" |
"projection": {
"projections": [
{ "field": "Id" },
{ "field": "Driver.Name" },
{ "field": "VehicleComposition.VehicleParent.Plate" }
]
}
WhereFilter — simple or composite
Represents the dynamic WHERE clause. It can be a simple filter or a filter tree (WhereTreeFilter).
Fields of a simple filter:
| Field | Description | Example |
|---|---|---|
property |
Property path (can be nested) | "OrderCompartments.Product.Id" |
value |
Value to compare | 12 |
filterType |
Comparison type (1 = Equals, 2 = NotEquals, etc.) | 1 |
operatorType |
Optional in simple filters | 0 (And), 1 (Or) |
Simple example:
"whereFilter": {
"property": "OrderCompartments.Product.Id",
"value": 12,
"filterType": 1,
"operatorType": 0
}
Composite (operands):
"whereFilter": {
"operatorType": 0,
"operands": [
{
"property": "Customer.Id",
"value": 5,
"filterType": 1
},
{
"property": "Status",
"value": "Pending",
"filterType": 1
}
]
}
This is equivalent to:
WHERE Customer.Id = 5 AND Status = 'Pending'
Full example
{
"pagination": {
"orderBy": "Driver.Name",
"pageNumber": 1,
"pageSize": 10
},
"projection": {
"projections": [
{ "field": "Id" },
{ "field": "Number" },
{ "field": "Type" },
{ "field": "Status" },
{ "field": "Modal" },
{ "field": "ScheduledDate" },
{ "field": "IsCanceled" },
{ "field": "Carrier.Name" },
{ "field": "Customer.Name" },
{ "field": "Driver.Name" },
{ "field": "Driver.FederalIdentification" },
{ "field": "OrderTelemetry.Id" },
{ "field": "OrderTelemetry.OrderId" },
{ "field": "OrderTelemetry.RegisteredById" },
{ "field": "OrderTelemetry.Status" },
{ "field": "OrderTelemetry.CreatedAt" },
{ "field": "OrderTelemetry.Interval" },
{ "field": "VehicleComposition.VehicleParent.Plate" }
]
},
"whereFilter": {
"property": "OrderCompartments.Product.Id",
"value": 12,
"filterType": 1,
"operatorType": 0
}
}
API Reference
DynamicQueryExtensions
public static class DynamicQueryExtensions
{
public static IQueryable<T> WhereDynamic<T>(this IQueryable<T> source, WhereTreeFilter filter);
public static IQueryable<object> SelectDynamic<TEntity>(this IQueryable<TEntity> source, IEnumerable<ProjectionField> fields);
public static IQueryable<object> SelectDynamic<TEntity>(this IQueryable<TEntity> source, IEnumerable<string> fields);
// Helpers for DynamicQueryContainer
public static bool HasPagination(this DynamicQueryContainer container);
public static bool HasProjection(this DynamicQueryContainer container);
public static bool HasWhereTreeFilter(this DynamicQueryContainer container);
}
WhereFilters
public record WhereFilter
{
public string? Property { get; init; } // supports null-safe segments: "Address?.City"
public WhereFilterType FilterType { get; init; }
public object? Value { get; init; }
}
public record WhereTreeFilter : WhereFilter
{
public WhereTreeFilter[]? Operands { get; init; }
public WhereOperatorType OperatorType { get; init; }
}
public static class WhereFilterInterpreter
{
public static Expression<Func<T, bool>> BuildExpression<T>(WhereTreeFilter root);
}
public enum WhereFilterType { /* Equal, NotEqual, LessThan, ... In, NotIn, Between, Contains, NotContains, StartsWith, NotStartsWith, EndsWith, NotEndsWith, Any, NotAny, IsNull, IsNotNull, IsEmpty, IsNotEmpty, IsNullOrEmpty, IsNotNullOrEmpty */ }
public enum WhereOperatorType { And, Or }
WhereFilterType (enum)
| Value | Description |
|---|---|
| None | No filter |
| Equal | Property equals value |
| NotEqual | Property not equals value |
| LessThan | Property less than value |
| GreaterThan | Property greater than value |
| LessThanOrEqual | Property less than or equal to value |
| GreaterThanOrEqual | Property greater than or equal to value |
| Between | Property is within [low, high] |
| In | Property is in a set |
| NotIn | Property is not in a set |
| Contains | String contains value |
| NotContains | String does not contain value |
| StartsWith | String starts with value |
| NotStartsWith | String does not start with value |
| EndsWith | String ends with value |
| NotEndsWith | String does not end with value |
| Any | Collection has any element |
| NotAny | Collection has no elements |
| IsNull | Property is null |
| IsNotNull | Property is not null |
| IsEmpty | String or collection is empty |
| IsNotEmpty | String or collection is not empty |
| IsNullOrEmpty | Null or empty |
| IsNotNullOrEmpty | Not null and not empty |
WhereOperatorType (enum)
| Value | Description |
|---|---|
| And | Logical AND group |
| Or | Logical OR group |
Projections
public record ProjectionField(string Field);
public record ProjectionTree { public ProjectionField[]? Projections { get; init; } }
public static class ProjectionInterpreter
{
public static Expression<Func<TEntity, TProjection>> BuildExpression<TEntity, TProjection>(ProjectionTree projectionTree);
public static Expression<Func<TEntity, TProjection>> BuildExpression<TEntity, TProjection>(IEnumerable<ProjectionField> fields);
public static Expression<Func<TEntity, TProjection>> BuildExpression<TEntity, TProjection>(IEnumerable<string> fields);
}
Guardrails
public sealed record DynamicQueryGuardrailOptions
{
public static DynamicQueryGuardrailOptions Default { get; }
public int MaxWhereTreeDepth { get; init; }
public int MaxWhereNodeCount { get; init; }
public int MaxFilterValueCount { get; init; }
public int MaxWherePathSegmentCount { get; init; }
public int MaxProjectionFieldCount { get; init; }
public int MaxProjectionPathSegmentCount { get; init; }
public int MaxCollectionNavigationCount { get; init; }
}
public sealed class DynamicQueryGuardrailException : InvalidOperationException
{
public DynamicQueryGuardrailException(string message);
}
ObjectSearch (optional)
public abstract class ObjectSearchComponent<TObjectSearch> where TObjectSearch : class
{
public virtual void Add(ObjectSearchLeaf<TObjectSearch>? component);
public Expression<Func<TObjectSearch, bool>>? BuildExpression();
}
public sealed class ObjectSearchComposite<TObjectSearch> : ObjectSearchComponent<TObjectSearch> where TObjectSearch : class { }
public sealed class ObjectSearchLeaf<TObjectSearch> : ObjectSearchComponent<TObjectSearch> where TObjectSearch : class { }
ObjectSearchValueType (enum)
| Value | Meaning |
|---|---|
None |
Null or undefined value |
IsPrimitive |
Simple value (string, int, GUID, DateTime, etc.) |
IsEnumerable |
List of values (IEnumerable) – used for In, Any, etc. filters |
References
- LINQ expressions: https://learn.microsoft.com/dotnet/csharp/expressions
- EF Core queries: https://learn.microsoft.com/ef/core/querying/
- Changelog
License
This project is licensed under the MIT License. See the LICENSE file at the repository root for details.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net10.0 is compatible. 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. |
-
net10.0
- NetToolsKit.Data (>= 8.0.0-preview.1)
NuGet packages (1)
Showing the top 1 NuGet packages that depend on NetToolsKit.DynamicQuery:
| Package | Downloads |
|---|---|
|
NetToolsKit.Data.EntityFrameworkCore
Lightweight helpers and patterns to tame EF Core setup and data access. Features: • Repository and Unit of Work abstractions. • Sensible defaults for DbContext configuration. • Migration and schema management helpers. • LINQ utilities and dynamic query support. Ideal for teams standardizing EF Core across services. |
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 8.0.0-preview.1 | 111 | 3/10/2026 |