Skip to content

v3.0.3

Choose a tag to compare

@github-actions github-actions released this 23 Jun 10:06

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
  • colId matching 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.Id and AgGridValueColumn.Id added to capture the AG Grid column identifier, enabling correct resolution when colId != 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!
  • AgGridRequest improvement: Added GroupKeys property for handling SSRM grouping levels!
  • Updated ApplyAgGridRequest: Correctly replaces grouping and aggregates for proper SSRM store state!

3. QueryResult Enhancements

  • New ResultCount property: Separate count for grouped/distinct queries vs TotalCount:
    • 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() to ISqlTranslator for 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 in ParserUtilities has been updated — see the migration section below.

5. New Test Coverage

  • Added AgGridResponseConverterTests
  • Added ResultCountTests
  • Added SqlTranslatorGroupedTests for Dapper grouped queries
  • Added GroupedQueryExecutionTests for 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.ResultCount

Upgrading

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.3

If 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.