FlexQuery.NET.AspNetCore 3.0.3

There is a newer version of this package available.
See the version list below for details.
dotnet add package FlexQuery.NET.AspNetCore --version 3.0.3
                    
NuGet\Install-Package FlexQuery.NET.AspNetCore -Version 3.0.3
                    
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="FlexQuery.NET.AspNetCore" Version="3.0.3" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="FlexQuery.NET.AspNetCore" Version="3.0.3" />
                    
Directory.Packages.props
<PackageReference Include="FlexQuery.NET.AspNetCore" />
                    
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 FlexQuery.NET.AspNetCore --version 3.0.3
                    
#r "nuget: FlexQuery.NET.AspNetCore, 3.0.3"
                    
#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 FlexQuery.NET.AspNetCore@3.0.3
                    
#: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=FlexQuery.NET.AspNetCore&version=3.0.3
                    
Install as a Cake Addin
#tool nuget:?package=FlexQuery.NET.AspNetCore&version=3.0.3
                    
Install as a Cake Tool

FlexQuery.NET

Dynamic filtering, sorting, paging, and projection for IQueryable in .NET.

NuGet Version NuGet Downloads Dotnet Support Documentation License


FlexQuery.NET is a lightweight and powerful dynamic query engine for .NET. It allows you to transform complex API query parameters into optimized, EF Core-translatable expression trees with a single line of code.

⚡ Key Features

  • Dynamic Querying: Powerful DSL, JQL, and JSON-based filtering.
  • IQueryable-Native: 100% server-side translation—no client-side evaluation.
  • Advanced Projection: Automatic SQL SELECT optimization including nested includes.
  • Governance & Security: Built-in field-level validation and operator restrictions.
  • High Performance: Thread-safe expression caching for ultra-low latency.

🚀 Quick Start

1. Installation

dotnet add package FlexQuery.NET
dotnet add package FlexQuery.NET.EntityFrameworkCore
dotnet add package FlexQuery.NET.Dapper
dotnet add package FlexQuery.NET.AspNetCore
dotnet add package FlexQuery.NET.Adapters.AgGrid

2. Entity Framework Core (Default)

Securely execute a dynamic query directly from your controller against an EF Core DbContext. The provider handles translation, pagination, and async execution automatically.

[HttpGet("users")]
public async Task<IActionResult> GetUsers([FromQuery] FlexQueryParameters parameters)
{
    var result = await _context.Users.FlexQueryAsync(parameters, options => 
    {
        options.AllowedFields = new HashSet<string> { "Id", "Name", "Email", "Status" };
        options.StrictFieldValidation = true;
        options.UseNoTracking = true; // Optimization for read-only queries
    });

    return Ok(result);
}

3. Dapper & Raw SQL Integration

For high-performance API endpoints or non-EF Core projects, use the Dapper provider to generate secure, dialect-aware, fully parameterized SQL queries.

using FlexQuery.NET.Dapper;
using FlexQuery.NET.Dapper.Dialects;

[HttpGet("users")]
public async Task<IActionResult> GetUsersDapper([FromQuery] FlexQueryParameters parameters)
{
    using var connection = new SqlConnection("Server=...;");    
    // Generates parameterized SQL, handles dialects (SQL Server, Postgres, MySQL, etc.)
    var result = await connection.FlexQueryAsync<User>(parameters, options => 
    {
        options.Dialect = new SqlServerDialect(); 
        options.AllowedFields = new HashSet<string> { "Id", "Name", "Email" };
    });

    return Ok(result);
}

4. AG Grid Adapter

FlexQuery.NET.Adapters.AgGrid parses AG Grid's Enterprise Server-Side Row Model JSON payloads natively, translating pagination, filtering, sorting, row grouping, and aggregations into FlexQuery operations.

[HttpPost("grid")]
public async Task<IActionResult> GetGridData([FromBody] AgGridRequest request)
{
    // 1. Parse AG Grid request into canonical QueryOptions
    var options = request.ToQueryOptions();

    // 2. Execute via EF Core or Dapper
    var result = await _context.Users.FlexQueryAsync<User>(options, opts =>
    {
        opts.AllowedFields = new HashSet<string> { "Id", "Name", "Status", "CreatedAt" };
    });

    // 3. Return format expected by AG Grid.
    // Grouped SSRM responses should prefer ResultCount when available.
    return Ok(new { rowData = result.Data, rowCount = result.ResultCount ?? result.TotalCount });
}

5. MiniOData Parser

Migrating from OData? FlexQuery.NET.Parsers.MiniOData acts as a drop-in bridge, automatically detecting and parsing OData syntax ($filter, $orderby, $top, $skip) on the same endpoint that handles JSON and JQL queries.

// Program.cs
builder.Services.AddFlexQueryMiniOData();

// Controller
[HttpGet("products")]
public async Task<IActionResult> GetProducts([FromQuery] FlexQueryParameters parameters)
{
    // Auto-detects OData parameters like:
    // ?$filter=Price gt 50 and Category eq 'Electronics'&$orderby=Name desc
    var result = await _context.Products.FlexQueryAsync(parameters);
    return Ok(result);
}

6. Example Query Requests

FlexQuery unifies multiple formats under the same API without configuration:

# Native DSL
GET /api/users?filter=age:gt:18&sort=createdAt:desc&page=1&pageSize=20

# JQL Syntax
GET /api/users?filter=Age > 18 AND Status = 'Active'

# OData Syntax (requires MiniOData package)
GET /api/users?$filter=Age gt 18 and Status eq 'Active'

📚 Documentation

Counting Semantics

QueryResult<T> exposes three different row counts. They answer different questions:

Property Meaning
TotalCount Filtered source records
ResultCount Shaped rows before paging
Data.Count Current page rows

For a normal query, TotalCount and ResultCount usually match:

1432 products
pageSize = 20

TotalCount  = 1432
ResultCount = 1432
Data.Count  = 20

For grouped or shaped queries, ResultCount is the count most UI grids need for paging:

1432 products
GROUP BY Brand

4 brand groups

TotalCount  = 1432
ResultCount = 4
Data.Count  = current page of groups

HAVING is applied before ResultCount:

1432 products
GROUP BY Brand
HAVING SUM(Quantity) > 100

2 groups remain

TotalCount  = 1432
ResultCount = 2

For AG Grid SSRM grouping, prefer:

var rowCount = result.ResultCount ?? result.TotalCount;

TotalCount semantics are unchanged for backward compatibility.

For detailed guides, API references, and advanced scenarios, visit our documentation site:

👉 https://flexquery.vercel.app


📄 License

FlexQuery.NET is licensed under the MIT License.

Product Compatible and additional computed target framework versions.
.NET net6.0 is compatible.  net6.0-android was computed.  net6.0-ios was computed.  net6.0-maccatalyst was computed.  net6.0-macos was computed.  net6.0-tvos was computed.  net6.0-windows was computed.  net7.0 was computed.  net7.0-android was computed.  net7.0-ios was computed.  net7.0-maccatalyst was computed.  net7.0-macos was computed.  net7.0-tvos was computed.  net7.0-windows was computed.  net8.0 is compatible.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 was computed.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  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

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
3.1.0 42 6/26/2026
3.0.6 81 6/24/2026
3.0.5 87 6/24/2026
3.0.4 85 6/23/2026
3.0.3 89 6/23/2026
3.0.2 99 6/22/2026
3.0.1 101 6/22/2026
2.5.0 106 5/13/2026
2.4.0 103 5/10/2026
2.3.1 105 5/8/2026
2.3.0 97 5/7/2026
2.2.0 117 5/6/2026
2.1.0 97 5/6/2026
2.0.1 90 5/6/2026
1.7.2 100 5/4/2026
1.7.1 98 5/2/2026
1.6.2 104 5/2/2026

# 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:**
```json
{
 "SUM_Total": 1500,
 "COUNT_Id": 12
}
```

**After:**

```json
{
 "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:

```csharp
// 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:**
```csharp
// 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:**
```json
// Before
{ "CustomerId": 1, "SUM_Total": 1250.00, "COUNT_Id": 3 }

// After
{ "CustomerId": 1, "totalSum": 1250.00, "idCount": 3 }
```

**SQL diff (Dapper):**
```sql
-- 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
```csharp
[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
```csharp
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:
```bash
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.