FlexQuery.NET.AspNetCore
3.0.3
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
<PackageReference Include="FlexQuery.NET.AspNetCore" Version="3.0.3" />
<PackageVersion Include="FlexQuery.NET.AspNetCore" Version="3.0.3" />
<PackageReference Include="FlexQuery.NET.AspNetCore" />
paket add FlexQuery.NET.AspNetCore --version 3.0.3
#r "nuget: FlexQuery.NET.AspNetCore, 3.0.3"
#:package FlexQuery.NET.AspNetCore@3.0.3
#addin nuget:?package=FlexQuery.NET.AspNetCore&version=3.0.3
#tool nuget:?package=FlexQuery.NET.AspNetCore&version=3.0.3
FlexQuery.NET
Dynamic filtering, sorting, paging, and projection for IQueryable in .NET.
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
SELECToptimization 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
Quick Links
- Getting Started
- Query Composition
- Security & Field Access
- Dapper Provider
- AG Grid Integration
- MiniOData Parser
📄 License
FlexQuery.NET is licensed under the MIT License.
| Product | Versions 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. |
-
net10.0
- FlexQuery.NET (>= 3.0.3)
- FlexQuery.NET.EntityFrameworkCore (>= 3.0.3)
-
net6.0
- FlexQuery.NET (>= 3.0.3)
- FlexQuery.NET.EntityFrameworkCore (>= 3.0.3)
-
net8.0
- FlexQuery.NET (>= 3.0.3)
- FlexQuery.NET.EntityFrameworkCore (>= 3.0.3)
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.