v3.0.3
FlexQuery.NET v3.0.3 Release Notes
Release date: 2026-06-23
Overview
v3.0.3 fixes AG Grid SSRM grouped sort validation by resolving aggregate and group-key sorts against the current grouped projection, preventing invalid SQL in Dapper and non-deterministic pagination in EF Core. Also introduces Server-Side Row Model response support, Dapper grouping and distinct, QueryResult.ResultCount, and the aggregate alias naming convention redesign.
What's New
1. AG Grid SSRM Grouped Sort Validation
Problem: sortModel entries were passed through unchanged to the SQL layer. At grouped levels, aggregate sorts like colId: "price" (with aggFunc: "AVG") generated ORDER BY Price instead of ORDER BY priceAvg. Detail-column sorts like colId: "id" reached GROUP BY queries and broke. Empty sortModels caused SQL Server pagination to crash (OFFSET without ORDER BY).
Fix: AgGridQueryOptionsParser.Parse() now validates and resolves every sortModel entry against the current grouped projection:
| Scenario | Before | After |
|---|---|---|
Aggregate sort (colId: "price", aggFunc: "AVG") |
ORDER BY [Price] (Dapper: broken SQL) |
ORDER BY [priceAvg] |
Detail sort (colId: "id") |
ORDER BY [Id] (broken in GROUP BY) |
Removed silently |
| All sorts invalid or empty | No ORDER BY (pagination crash) | Fallback: category ASC |
colId != field (id: "avg_price", field: "price") |
ORDER BY [avg_price] (broken) |
ORDER BY [priceAvg] |
Nested group (groupKeys: ["Electronics"]) |
Same broken SQL | Validated against brand, priceAvg, quantitySum |
| Ungrouped / leaf level | Pass through | Pass through (unchanged) |
Resolution chain:
sortModel.colId → valueCol/id lookup → BuildAggregateAlias(aggFunc, field) → SortNode.Field
sortModel.colId → rowGroupCol[id] lookup → GetProjectionName(field) → SortNode.Field
Key details:
"average"normalized to"avg"in the sort validation path, consistent with the aggregate builder- Fallback sort uses the current group field's projection name (via
GetProjectionName) - Only the current group column (at
rowGroupCols[groupKeys.Count]) is valid — parent group keys are not in the projection colIdmatching uses ordinal comparison (column IDs are case-sensitive in AG Grid)- Zero changes to the EF Core pipeline, Dapper translator, QueryBuilder, or GroupByBuilder
New column models:
AgGridGroupColumn.IdandAgGridValueColumn.Idadded to capture the AG Grid column identifier, enabling correct resolution whencolId != field
2. AgGrid SSRM Response Support
- New
ToAgGridServerSideResponse()extension method: Converts QueryResult directly to AgGrid Server-Side Row Model compatible response! - New
AgGridResponseConverter: Handles both group rows and leaf rows for SSRM! - New models:
AgGridGroupRow,AgGridLeafRow,AgGridResponseFieldOptions,AgGridServerSideResponse! AgGridRequestimprovement: AddedGroupKeysproperty for handling SSRM grouping levels!- Updated
ApplyAgGridRequest: Correctly replaces grouping and aggregates for proper SSRM store state!
3. QueryResult Enhancements
- New
ResultCountproperty: Separate count for grouped/distinct queries vsTotalCount:TotalCount: Total source records (before grouping/distinct)ResultCount: Rows produced by final query (after grouping/distinct)
4. Dapper Grouping & Distinct Support
- Added full GROUP BY and DISTINCT support to Dapper provider!
- Added
TranslateSourceCount()toISqlTranslatorfor source record count! - Added
ExtractCountSql()helper to get count of final shaped results! - Enhanced
ExecuteQueryAsync()to calculate both TotalCount and ResultCount!
5. Aggregate Alias Convention Redesign
Aggregate aliases now use a field-first, camelCase format instead of FUNCTION_Field.
| Syntax | Before | After |
|---|---|---|
sum(Total) |
SUM_Total |
totalSum |
count(Id) |
COUNT_Id |
idCount |
avg(Price) |
AVG_Price |
priceAvg |
min(Total) |
MIN_Total |
totalMin |
max(Total) |
MAX_Total |
totalMax |
count() |
COUNT_All |
allCount |
avg(Order.Total) |
AVG_Order_Total |
orderTotalAvg |
Why?
- Improves JSON serialization compatibility.
- Avoids serializer-generated names such as
suM_Total. - Provides more natural JavaScript and TypeScript property names.
- Improves integration with AG Grid, PrimeVue, React, and other frontend data grids.
- Establishes a consistent long-term aggregate naming convention.
Affected areas
If you reference aggregate aliases directly, update:
- Aggregate sorting fields
- HAVING expressions
- AG Grid column bindings
- Frontend property access
- Custom projections and integration tests
Example:
Before:
{
"SUM_Total": 1500,
"COUNT_Id": 12
}After:
{
"totalSum": 1500,
"idCount": 12
}Benefits:
-
Better JSON serialization — no awkward transformer artifacts (
SUm_Quantity) -
Natural JavaScript/TypeScript property names
-
Lexical grouping of related aggregates (
priceAvg,priceMin,priceSum) -
Cleaner API contracts
-
Consistent AG Grid / PrimeVue / React data binding
-
All built-in adapters (AG Grid, Kendo) generate the new format automatically.
-
The
BuildAggregateAlias()utility method inParserUtilitieshas been updated — see the migration section below.
5. New Test Coverage
- Added
AgGridResponseConverterTests - Added
ResultCountTests - Added
SqlTranslatorGroupedTestsfor Dapper grouped queries - Added
GroupedQueryExecutionTestsfor Dapper API grouped queries
6. Documentation Updates
- Updated AgGrid adapter docs with SSRM features and new alias convention
- Updated migration guide (v2-to-v3) with aggregate alias migration steps
- Updated grouping, projection, and examples docs with new alias format
- Updated Dapper provider docs (ef-core.md, sql-generation.md) with new aliases
Migration Guide
Aggregate Alias Migration
If you programmatically construct AggregateModel instances with explicit Alias values, update them to the new format:
// Before
options.Aggregates.Add(new AggregateModel
{
Field = "Total",
Function = "sum",
Alias = "SUM_Total" // ← old format
});
// After
options.Aggregates.Add(new AggregateModel
{
Field = "Total",
Function = "sum",
Alias = "totalSum" // ← new format
});If you use the built-in parsers (HTTP query parameters, AG Grid adapter, Kendo adapter), the aliases are generated automatically — no code changes needed.
Sort by aggregate alias:
// Before
options.Sort = [new SortNode { Field = "SUM_Total", Descending = true }];
// After
options.Sort = [new SortNode { Field = "totalSum", Descending = true }];HAVING clauses do not require changes to the HavingCondition itself (it uses function + field, not the alias). The alias is resolved internally by BuildAggregateAlias().
JSON response diff:
// Before
{ "CustomerId": 1, "SUM_Total": 1250.00, "COUNT_Id": 3 }
// After
{ "CustomerId": 1, "totalSum": 1250.00, "idCount": 3 }SQL diff (Dapper):
-- Before
SELECT SUM("Total") AS "SUM_Total" FROM "Orders" GROUP BY "CustomerId"
-- After
SELECT SUM("Total") AS "totalSum" FROM "Orders" GROUP BY "CustomerId"Using new AgGrid SSRM response
[HttpPost("grid")]
public async Task<IActionResult> GetGridData([FromBody] AgGridRequest request)
{
var options = request.ToQueryOptions();
var result = await _context.Products.FlexQueryAsync<Product>(options);
var agGridResponse = result.ToAgGridServerSideResponse(request);
return Ok(agGridResponse);
}Using QueryResult.ResultCount
var result = await dbContext.Products.FlexQueryAsync<Product>(options);
// Total records in source table (after filters): result.TotalCount
// Number of groups/rows after grouping/distinct: result.ResultCountUpgrading
Update all relevant packages to v3.0.3:
dotnet add package FlexQuery.NET --version 3.0.3
dotnet add package FlexQuery.NET.Adapters.AgGrid --version 3.0.3
dotnet add package FlexQuery.NET.Dapper --version 3.0.3If you have hardcoded aggregate alias strings anywhere in your application code (sort fields, alias overrides, response parsing), update them to the new field-first camelCase format. No other migration steps are required.