Migrate from v3.1.1 to v4
This document is based on a code-level comparison of the v3.1.1 release against
the current v4 implementation. It lists only verified, developer-affecting
changes. For a compact mapping of areas to documentation, see the
Change Matrix.
What changed at a glance
- Configuration model rebuilt — DI registration replaced by immutable static facades.
- Typed DTO projection added —
FlexQueryAsync<TEntity, TResponse>with mapping. - Expand added — replaces
FilteredIncludeswith deep, filtered, sorted trees. - Keyset pagination added —
cursor+NextCursorToken+SeekAfter. - Aggregates reworked — dedicated
aggregateparameter, typed enum, HAVING tree. - FQL parser replaces JQL — package, enum, and exception renames.
- Validation extended — the v3 rule pipeline gains expand/having/sort coverage.
- OpenAPI package added.
- Legacy syntaxes removed — JSON, Indexed, and Generic query syntaxes are gone.
New features
| Feature | v3.1.1 equivalent | Where to read |
|---|---|---|
Typed DTO FlexQueryAsync<TEntity, TResponse> (4 EF + 4 Dapper overloads) | none | Typed DTO Projection |
Mapping (CreateMap, ForMember, ForNavigation, FlexQueryMapping registry) | MapField only | Typed DTO Projection |
expand trees (filter/sort/take per branch) | FilteredIncludes | Expand |
Keyset pagination (useKeysetPagination, cursor, NextCursorToken, SeekAfter) | none | Keyset Pagination |
Query.Create() fluent builder (with FilterGroupBuilder) | FilterBuilder only | Fluent API |
ResultShape output surface + JSON converter | none | Query Result |
Governance extensions ([FieldAccess(AllowedIncludes)], options class split) | governance sets on BaseQueryOptions | Security |
CancellationToken on all async overloads | none | EF Core |
FlexQuery.NET.OpenApi package | none | OpenAPI |
Dapper ModelBuilder + IEntityTypeConfiguration<T> | MappingRegistry | Dapper |
| Dapper SQL execution logging with DECLARE scripts | none | Dapper |
DSL AND/OR keywords | symbolic & / | only | Query Syntax |
select aliases (field:alias, field as alias) | none | Projection |
Renamed
| v3.1.1 | v4 | Migration |
|---|---|---|
Package FlexQuery.NET.Parsers.Jql | FlexQuery.NET.Parsers.Fql | Update package reference. |
QuerySyntax.Jql | QuerySyntax.Fql | Find/replace. |
JqlParseException : Exception | FqlParseException : FlexQueryException | Update catch blocks. |
QueryOptions.FilteredIncludes | QueryOptions.Expand | Find/replace; see Expand page for new syntax. |
ApplyFilteredIncludes<T>() | ApplyExpand<T>() | Find/replace. |
AggregateModel (string function) | Aggregate (typed AggregateFunction) | Update construction sites. |
HavingCondition | HavingNode tree (HavingLogicalNode/HavingConditionNode/HavingGroupNode) | Update construction sites. |
DebugResult | QueryDebugInfo | Find/replace. |
Models.IFlexQueryExecutionListener | Execution.IFlexQueryExecutionListener | Update using directives (members unchanged). |
Models.QueryContext | Execution.QueryContext (now sealed) | Update using directives. |
Models.BaseQueryOptions | split into Options.BaseQueryOptions + Options.QueryGovernanceOptions | Adjust base-class references. |
Note: SortOption.cs → SortNode was a file rename only — the type was already named
SortNode at v3.1.1. No code change is required for it.
Removed
| Removed | Replacement |
|---|---|
JSON / Indexed / Generic query syntaxes (JsonQueryParser, AutoDetect) | Native DSL, FQL, or MiniOData |
CaseInsensitive / CaseInsensitiveFields options | — (comparisons follow provider semantics) |
Parser DI registration (ServiceCollectionExtensions in parser packages, MiniODataFeature) | Static Fql.Register() / MiniOData.Register() |
Deprecated QueryOptions members: Skip, Top, EnableCache, Items, Ast | PagingOptions, per-call options |
InvalidFilterFieldException / InvalidSortFieldException | QueryValidationException with structured errors |
Manual Dapper Dialect config (ISqlDialectResolver, DefaultSqlDialectResolver) | Auto-detection from the DbConnection |
Dapper MappingRegistry/IMappingRegistry/IEntityMapping/JoinInfo | ModelBuilder + IEntityTypeConfiguration<T> |
Dapper conventions (IEntityConvention, IForeignKeyConvention, IRelationshipConvention, Default*) | Convention-first defaults (now internal) |
QueryableAspNetCoreExtensions.FlexQueryAsync | Provider FlexQueryAsync + [FieldAccess] filter |
FromAgGridJson(string) / FromKendoJson(string) | JsonElement.ToQueryOptions() |
AgGridQueryOptionsParser / AgGridResponseConverter / KendoQueryOptionsParser | ToQueryOptions() / ToAgGridServerSideResponse() extensions |
UseSplitQuery option | Split-query include hydration is now internal behavior |
Public caches (ExpressionCache, ParserCache, ProjectionExpressionCache) | Internal caching (FlexQueryCacheSettings remains public) |
Public helpers (ExpressionBuilder, QueryBuilder, ProjectionOptimizer, GovernanceValidator, DynamicTypeBuilder, SelectTreeBuilder, ExpressionPrinter, ExpressionTreeVisualizer, ProjectionMetadata*) | Not replaced — internal implementation detail |
FlexQueryParameters.RawParameters (public) | Internal — use model binding |
Changed
Configuration and registration
// v3.1.1 - DI-era registration
services.AddFlexQueryDapper(...);
services.AddFlexQueryMiniOData(...);
// v4 - static facades, immutable after first use
FlexQueryCore.Configure(options => { ... });
FlexQueryDapper.Configure(options => { ... });
FlexQueryEFCore.Configure(options => options.UseNoTracking = true);
MiniOData.Register();Calling any Configure after a query has executed throws InvalidOperationException.
No-tracking
// v3.1.1 - QueryExecutionOptions.UseNoTracking = true (default), UseSplitQuery
// v4 - provider facade or per-call:
FlexQueryEFCore.Configure(options => options.UseNoTracking = true); // global
opt.UseNoTracking = false; // per call (EfCoreQueryOptions)DSL logical operators
# v3.1.1 - symbolic only
filter=Status:eq:Active & Age:gte:18
# v4 - keywords AND symbolic both accepted
filter=Status:eq:Active AND Age:gte:18
filter=Status:eq:Active & Age:gte:18The symbolic forms still work — this is an additive change. New in v4: AND/OR are
reserved and cannot appear as unquoted values (name:eq:"AND" is required).
Aggregate syntax
# v3.1.1 - aggregates inside select
select=Status,sum(Total),count(Id)
# v4 - dedicated aggregate parameter
select=Status&aggregate=sum:Total,count:IdAliases are PascalCase by default (SumTotal); explicit aliasing:
aggregate=sum:Total:totalSales.
HAVING
Every aggregate referenced in having must be declared in aggregate (v3.1.1's
alias-integrity rule is replaced by declared-aggregate enforcement). having without
groupBy is rejected.
Paging validation
# v3.1.1 - malformed page values were loosely handled
# v4 - parse throws:
page=abc → QueryParseException: '...' is not a valid page number. Page must be a positive integer.
pageSize=-5 → QueryParseException: '...' is not a valid page size. PageSize must be a positive integer.
distinct=x → QueryParseException: '...' is not a valid distinct value. Distinct must be 'true' or 'false'.Out-of-range values (e.g. pageSize=99999) are clamped to MaxPageSize instead of erroring.
Exceptions
// v3.1.1
catch (InvalidFilterFieldException ex) { ... }
catch (InvalidSortFieldException ex) { ... }
// v4 - unified hierarchy rooted at FlexQueryException
catch (QueryValidationException ex) { ... } // field access violations
catch (QueryParseException ex) { ... } // malformed parameters
catch (FlexQueryException ex) { ... } // safety net for all FlexQuery errorsDapper model definition
// v3.1.1 - MappingRegistry
var registry = new MappingRegistry();
registry.Register<Customer>(...);
// v4 - ModelBuilder with EF-style configuration
FlexQueryDapper.Configure(options =>
{
options.Model.Entity<Customer>()
.ToTable("Customers")
.HasKey(c => c.Id)
.HasMany(c => c.Orders)
.HasForeignKey("CustomerId");
});The dialect is auto-detected from the connection; DapperQueryOptions now derives from
QueryGovernanceOptions.
Provider overload shapes
- EF Core:
FlexQueryAsync<T>signatures now end withCancellationToken; four typedFlexQueryAsync<TEntity, TResponse>overloads were added. - Dapper: the five dynamic overloads became three (
FlexQueryParameters,IDictionary<string, StringValues>,QueryOptions) plus four typed overloads.
Provider behavior changes
- EF Core: include hydration is composed as EF Core filtered includes (the
UseSplitQuerytoggle is gone; the provider decides the SQL shape); expand branches support per-branch filter/sort/take; grouped queries execute through a dedicated grouped executor. - Dapper: dialect auto-detection; DTO-aware SQL generation with type-map field rewrites; include-only joins excluded from the count query; SQL execution logging.
Security / governance changes
- All governance members keep their names but move to
QueryGovernanceOptions. [FieldAccess]gainsAllowedIncludes(and the class/filter become sealed).- Expand paths are governed by
AllowedIncludes.
Integration changes
- AG Grid / Kendo:
From*Json(string)replaced byJsonElement.ToQueryOptions(); standalone parser/converter classes removed in favor of extension methods. - OpenAPI: new package for
Microsoft.AspNetCore.OpenApi(.NET 9/10) — Swashbuckle-era guidance is obsolete.
Migration steps
- Update package references (rename
Parsers.Jql→Parsers.Fql; addOpenApiif used). - Replace DI registration of parsers/providers with
Fql.Register(),MiniOData.Register(),FlexQueryEFCore.Configure(),FlexQueryDapper.Configure(). - Replace
FilteredIncludesusage withexpandsyntax. - For Dapper: define the entity model via
options.Model(tables, keys, relationships). - Move aggregates out of
selectintoaggregate; verifyhavingreferences declared aggregates. - Replace removed exception types with
QueryValidationExceptionhandling. - Remove
CaseInsensitiveconfiguration and JSON/Indexed/Generic syntax usage. - Replace
FromAgGridJson/FromKendoJsonwith theJsonElementoverloads. - Re-run test suites — paging parameter validation is stricter (malformed values now throw) and HAVING enforcement is stricter.