Lyo.Query
1.0.0
dotnet add package Lyo.Query --version 1.0.0
NuGet\Install-Package Lyo.Query -Version 1.0.0
<PackageReference Include="Lyo.Query" Version="1.0.0" />
<PackageVersion Include="Lyo.Query" Version="1.0.0" />
<PackageReference Include="Lyo.Query" />
paket add Lyo.Query --version 1.0.0
#r "nuget: Lyo.Query, 1.0.0"
#:package Lyo.Query@1.0.0
#addin nuget:?package=Lyo.Query&version=1.0.0
#tool nuget:?package=Lyo.Query&version=1.0.0
Lyo.Query
Execution engine for Lyo.Query.Models filter trees. IWhereClauseService / BaseWhereClauseService translates polymorphic WhereClause ASTs (condition / group, optional SubClause) into LINQ expression trees on IQueryable<T>, applies multi-key SortBy, and can evaluate / explain the same AST against a loaded entity in memory.
What this package is: AST → expression / matcher. What it is not: HTTP endpoints, QueryConcreteReq / ProjectionQueryReq / QueryReq builders, or query-result caching — those live in Models + Lyo.Api.
EF-agnostic at the boundary (works on any IQueryable, including EF DbSet and in-memory). Requires ICacheService + CacheOptions for compiled predicate / matcher / metadata caches.
Targets net10.0.
Features
- AST →
IQueryable—ApplyWhereClausebuilds EF-translatable (or provider) expression trees fromWhereClause - 16 comparators — Equals / NotEquals / Contains* / StartsWith* / EndsWith* / Greater* / Less* / In / NotIn / Regex / NotRegex
- Collection paths —
Lines.Quantity→Any(...)on collection elements; bare collection /.Countstyle paths compare against count - Two-phase SubClause —
includeSubClauses: falsefor SQL root pass; load includes then match sub-tree in memory - Multi-key sort —
SortByProperty+ApplyOrderingwithPriorityand stable default tie-break - In-memory match + explain —
MatchesWhereClause/ExplainMatchwith blocking path and OR-branch detail - ICache hot path — compiled EF predicates (
filter_ef_predicate:*), matchers (filter_matcher:*), sort keys, property-path metadata - Path validation —
TryValidatePropertyPath/InvalidQueryExceptionfor bad dotted paths - Contains→Regex coalesce — adjacent
Containsleaves on the same string field can merge into oneRegexalternation - Property diffing —
IPropertyComparisonServicefor patch/update pipelines - Value coercion —
IValueConversionServicefor JSON literals → CLR property types (suppressible whenLyo.Apiowns conversion)
Examples
Register services
using Lyo.Query;
services.AddLocalCache(/* … */); // or AddFusionCache(...)
services.AddLyoQueryServices();
// registers IValueConversionService, IPropertyComparisonService, IWhereClauseService (singletons)
// Hosting Lyo.Api (richer ITypeConversionService already registered):
// services.AddLyoQueryServices(registerValueConversion: false);
Apply WhereClause to IQueryable
using Lyo.Query;
using Lyo.Query.Models;
using Lyo.Query.Models.Builders;
var where = WhereClauseBuilder.And()
.Equals("Status", "Active")
.GreaterThan("Age", 18)
.Build();
IQueryable<Person> people = db.People.AsQueryable();
var filtered = whereClauseService.ApplyWhereClause(people, where);
var page = await filtered.Skip(20).Take(20).ToListAsync(ct);
Two-phase SubClause (SQL then in-memory)
// Root runs as SQL; SubClause refined after Include load
var where = WhereClauseBuilder.And()
.Equals("Status", "Open")
.AddSubClause(sub => sub.Contains("Tags", "vip"))
.Build();
var sqlPhase = whereClauseService.ApplyWhereClause(db.Orders, where, includeSubClauses: false);
var includePaths = whereClauseService.GetCollectionIncludePathsForWhereClause<Order>(where);
// apply Include(includePaths…) then materialize
var rows = await sqlPhase /* .Include(...) */ .ToListAsync(ct);
var matched = rows.Where(o => whereClauseService.MatchesWhereClause(o, where)).ToList();
Multi-key ordering
using Lyo.Query.Models;
using Lyo.Common.Enums;
var sorted = whereClauseService.ApplyOrdering(
queryable: people,
sortByProps:
[
new SortBy { PropertyName = "LastName", Direction = SortDirection.Ascending, Priority = 0 },
new SortBy { PropertyName = "FirstName", Direction = SortDirection.Ascending, Priority = 1 },
],
defaultOrder: p => p.Id,
defaultSortDirection: SortDirection.Ascending);
Match and ExplainMatch
var entity = await db.People.AsNoTracking().FirstAsync(ct);
if (whereClauseService.MatchesWhereClause(entity, where))
{
var explain = whereClauseService.ExplainMatch(entity, where);
// explain.Passed, BlockingPath, FailureSummary, Nodes (per-condition ActualValueSummary)
// In-memory only — not for SQL-only pipelines
}
Validate paths + collection includes
if (!whereClauseService.TryValidatePropertyPath<Person>("Addresses.City", out var err))
throw new InvalidOperationException(err);
foreach (var path in whereClauseService.GetCollectionIncludePathsForWhereClause<Person>(where))
query = query.Include(path); // e.g. "Addresses" for "Addresses.City"
WhereClauseHelpers fingerprints
using Lyo.Query.Services.WhereClause;
var hash = WhereClauseHelpers.GetWhereClauseTreeHash(where); // stable structural fingerprint
var hasSub = WhereClauseHelpers.HasAnySubClause(where);
if (WhereClauseHelpers.TryExtractConditions(where, out var conditions, out var op))
{
// flat And/Or leaves — false when SubClause or unsupported nesting present
}
Property diff for patch
var diffs = propertyComparisonService.GetPropertyDifferences(entity, patchDto);
// Dictionary<propertyName, newValue> — only changed writable props
Benchmarks
- Portfolio suite:
query
Package map
| Package | Responsibility |
|---|---|
Lyo.Query (this) |
WhereClause → LINQ / matcher; sort; explain; path metadata; ICache for compiled trees |
Lyo.Query.Models |
AST types, QueryConcreteReq / ProjectionQueryReq / QueryReq, builders, enums |
Lyo.Api |
POST …/QueryConcrete, /QueryProject, root /Query; query result caching; projection SQL |
Lyo.Query.Web.Components |
Blazor query workbench |
Builders and endpoint payloads are documented on Models / Api. This doc covers the runtime translator.
IWhereClauseService
BaseWhereClauseService is the default DI implementation.
| Member | Behavior |
|---|---|
ApplyWhereClause<T>(source, where, includeSubClauses = true) |
Compiles AST → Expression<Func<T,bool>>, caches under filter_ef_predicate:…, applies .Where. includeSubClauses: false skips SubClause chains (SQL phase). |
SortByProperty<T>(source, propertyName, direction?) |
Single dotted path OrderBy / OrderByDescending (default Desc when null). Collections order by count. |
ApplyOrdering<T>(…, sortByProps, defaultOrder, defaultSortDirection) |
Multi-key sort by SortBy.Priority then list order; always attaches default tie-break for stable paging. |
MatchesWhereClause<T>(entity, where) |
Compiled in-memory matcher (filter_matcher:… cache). |
ExplainMatch<T>(entity, where) |
WhereClauseExplainResult — per-node pass/fail, BlockingPath, OR-branch outcomes, SubClause chains. In-memory only (default interface throws). |
GetCollectionIncludePathsForWhereClause<T>(where) |
Distinct navigation prefixes that cross collections (for EF Include before sub-clause match). |
TryValidatePropertyPath<T>(name, out error) |
Preflight for sort / filter fields. |
Invalid paths / operators → InvalidQueryException (Lyo.Query.Models.Exceptions).
Comparators and path semantics
Operators come from ComparisonOperatorEnum on each ConditionClause:
| Operator | Notes |
|---|---|
Equals / NotEquals |
Simple comparison; null-safe rules enforced |
GreaterThan / GreaterThanOrEqual / LessThan / LessThanOrEqual |
On scalars: value compare. On collection navigations: compare against collection count |
Contains / NotContains / StartsWith / EndsWith / negations |
String methods when the target type supports them |
In / NotIn |
Value may be a list or CSV string |
Regex / NotRegex |
String regex; trivial patterns like .* short-circuit to true |
Dotted paths
- Scalar:
Status,Address.City - Into collection:
Lines.Quantity→lines.Any(e => e.Quantity …) - Collection count: path metadata
IsCountPathbuildsEnumerable.Countthen compares
Group optimization: within an AND/OR group, multiple Contains leaves on the same string field may be coalesced into a single case-insensitive Regex alternation before expression build.
Two-phase SubClause execution
WhereClause.SubClause (on a condition or group) supports split execution — used heavily by Lyo.Api query pipelines:
- Phase 1 (DB) —
ApplyWhereClause(..., includeSubClauses: false)so only the primary predicate becomes SQL. - Load —
GetCollectionIncludePathsForWhereClause→ EFIncludefor collection segments referenced by the sub-tree. - Phase 2 (memory) —
MatchesWhereClause/ExplainMatchon materialized entities with the full tree (includeSubClausesdefault true).
Helpers:
WhereClauseHelpers.HasAnySubClause(node)— detect whether a two-phase path is neededWhereClauseHelpers.TryExtractConditions— flatten simple And/Or trees for projection-level filtering (returnsfalseif anySubClause)WhereClauseHelpers.GetWhereClauseTreeHash— structural fingerprint for cache keys / logging (not cryptographic)
var where = WhereClauseBuilder.And()
.Equals("Age", 10)
.AddSubClause(sub => sub.AddAnd(s => s.Equals("Name", "Alice")))
.Build();
ICache usage (predicate / matcher cache)
AddLyoQueryServices requires ICacheService + CacheOptions (e.g. AddLocalCache / AddFusionCache). This is not the same as Lyo.Api query-result caching (QueryOptions.CacheQueryResultsAsUtf8Payload).
| Cache key prefix / pattern | Stores |
|---|---|
filter_ef_predicate:… |
Compiled Expression / predicate for ApplyWhereClause (keyed by entity + tree + includeSubClauses) |
filter_matcher:… |
Compiled in-memory matcher for MatchesWhereClause |
| Sort-key lambdas | Per (TEntity, propertyName) order key selectors |
| Property-path metadata | Resolved dotted-path segments, collection indexes, final CLR types |
SubQueryIncludePaths_… |
Include-path lists for a where tree |
| Property-comparison strategies | Per-property equality strategy for IPropertyComparisonService |
Tags typically include the entity CLR type so hosts can invalidate by type when schemas change.
Api result cache (optional UTF-8 payload entries for /QueryConcrete + /QueryProject) is documented under Lyo.Api — Query result caching.
IPropertyComparisonService
GetPropertyDifferences<TEntity, TOther>(entity, newData) walks public writable properties on TEntity that have a same-named readable property on TOther, compares with an inferred strategy (direct equality, enum/string coercion, or Convert.ChangeType), and returns Dictionary<string, object?> of changed property → proposed new value.
Used by patch/update pipelines (e.g. Lyo.Api) to build minimal diffs. Strategies are cached via ICacheService.
IValueConversionService
ConvertToTargetType(value, targetType)— JSON literals → CLR (primitives, nullables,Guid, enums, date/time, lists)GetUnderlyingType(type)— stripNullable<T>IsObjectEnumerable(value)— non-string/ non-byte[]IEnumerable
When hosting Lyo.Api, register Api’s richer ITypeConversionService (extends this interface) and call:
services.AddLyoQueryServices(registerValueConversion: false);
Otherwise two IValueConversionService registrations fight.
Registration
services.AddLocalCache(/* … */); // prerequisite
services.AddLyoQueryServices(registerValueConversion: true);
Registers as singletons:
IValueConversionService→ValueConversionService(unlessregisterValueConversion: false)IPropertyComparisonService→PropertyComparisonServiceIWhereClauseService→BaseWhereClauseService
Optional: IMetrics / ILogger are taken from DI when present (Constants.Metrics.*).
Metrics
When IMetrics is registered, BaseWhereClauseService emits under Lyo.Query.Constants.Metrics:
| Name | Kind |
|---|---|
query.filter.apply_query_node.duration |
histogram/timer |
query.filter.apply_query_node.success |
counter |
query.filter.sort_by_property.duration |
histogram/timer |
query.filter.sort_by_property.success |
counter |
query.filter.apply_ordering.duration |
histogram/timer |
query.filter.apply_ordering.success |
counter |
query.filter.matches_query_node.duration |
histogram/timer |
query.filter.matches_query_node.success |
counter |
query.filter.sort_by_count |
gauge |
Tags: entity_type, operation (Constants.Metrics.Tags).
Related docs
- DTOs / builders / request shapes →
Lyo.Query.Models - HTTP endpoints, projection SQL, result caching →
Lyo.ApiQuery & Request Builders - Blazor workbench →
Lyo.Query.Web.Components - Portfolio benchmarks suite →
query
Links
Dependencies
Generated from ProjectReference / PackageReference (same model as docs/Lyo.ProjectGraph.html).
Lyo.Cache— (direct, lyo)Lyo.Common— (direct, lyo)Lyo.Exceptions— (direct, lyo)Lyo.Metrics— (direct, lyo)Lyo.Query.Models— (direct, lyo)Microsoft.Extensions.DependencyInjection.Abstractions10.0.5— (direct, microsoft)Microsoft.Extensions.Logging.Abstractions10.0.5— (direct, microsoft)Lyo.Compression— (transitive, lyo)Lyo.Encryption— (transitive, lyo)Lyo.Hashing— (transitive, lyo)Lyo.Health— (transitive, lyo)Lyo.KeyStore— (transitive, lyo)Lyo.Result— (transitive, lyo)Lyo.Streams— (transitive, lyo)BouncyCastle.Cryptography2.6.2— (transitive, third-party, netstandard2.0)EasyCompressor2.1.0— (transitive, third-party)Konscious.Security.Cryptography.Argon21.3.1— (transitive, third-party)Microsoft.Bcl.AsyncInterfaces10.0.5— (transitive, microsoft, netstandard2.0)Microsoft.Extensions.Caching.Memory10.0.5— (transitive, microsoft)Microsoft.Extensions.Configuration.Binder10.0.5— (transitive, microsoft)Microsoft.Extensions.DependencyInjection10.0.5— (transitive, microsoft)Microsoft.Extensions.Options.ConfigurationExtensions10.0.5— (transitive, microsoft)System.Buffers4.6.1— (transitive, microsoft, netstandard2.0)System.IO.Hashing10.0.5— (transitive, microsoft, net10.0)System.Memory4.6.3— (transitive, microsoft, netstandard2.0)System.Text.Json10.0.5— (transitive, microsoft, netstandard2.0)System.Threading.Tasks.Extensions4.6.3— (transitive, microsoft, netstandard2.0)
| 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
- Lyo.Cache (>= 1.0.0)
- Lyo.Common (>= 1.0.0)
- Lyo.Exceptions (>= 1.0.0)
- Lyo.Metrics (>= 1.0.0)
- Lyo.Query.Models (>= 1.0.0)
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 10.0.5)
- Microsoft.Extensions.Logging.Abstractions (>= 10.0.5)
NuGet packages (1)
Showing the top 1 NuGet packages that depend on Lyo.Query:
| Package | Downloads |
|---|---|
|
Lyo.Api
Core API library for building RESTful APIs with Entity Framework Core, caching, and mapping support. |
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 1.0.0 | 76 | 8/16/2026 |