NetToolsKit.DynamicQuery 8.0.0-preview.1

This is a prerelease version of NetToolsKit.DynamicQuery.
dotnet add package NetToolsKit.DynamicQuery --version 8.0.0-preview.1
                    
NuGet\Install-Package NetToolsKit.DynamicQuery -Version 8.0.0-preview.1
                    
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="NetToolsKit.DynamicQuery" Version="8.0.0-preview.1" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="NetToolsKit.DynamicQuery" Version="8.0.0-preview.1" />
                    
Directory.Packages.props
<PackageReference Include="NetToolsKit.DynamicQuery" />
                    
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 NetToolsKit.DynamicQuery --version 8.0.0-preview.1
                    
#r "nuget: NetToolsKit.DynamicQuery, 8.0.0-preview.1"
                    
#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 NetToolsKit.DynamicQuery@8.0.0-preview.1
                    
#: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=NetToolsKit.DynamicQuery&version=8.0.0-preview.1&prerelease
                    
Install as a Cake Addin
#tool nuget:?package=NetToolsKit.DynamicQuery&version=8.0.0-preview.1&prerelease
                    
Install as a Cake Tool

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


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


License

This project is licensed under the MIT License. See the LICENSE file at the repository root for details.


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

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