FlexQuery.NET.AspNetCore
3.1.0
dotnet add package FlexQuery.NET.AspNetCore --version 3.1.0
NuGet\Install-Package FlexQuery.NET.AspNetCore -Version 3.1.0
<PackageReference Include="FlexQuery.NET.AspNetCore" Version="3.1.0" />
<PackageVersion Include="FlexQuery.NET.AspNetCore" Version="3.1.0" />
<PackageReference Include="FlexQuery.NET.AspNetCore" />
paket add FlexQuery.NET.AspNetCore --version 3.1.0
#r "nuget: FlexQuery.NET.AspNetCore, 3.1.0"
#:package FlexQuery.NET.AspNetCore@3.1.0
#addin nuget:?package=FlexQuery.NET.AspNetCore&version=3.1.0
#tool nuget:?package=FlexQuery.NET.AspNetCore&version=3.1.0
FlexQuery.NET.AspNetCore
ASP.NET Core integration with declarative field-access security.
When to Use This Package
Install this package when you want to use [FieldAccess] attributes on your API controllers to declare per-endpoint security rules, or when you need automatic model binding for FlexQueryParameters.
Installation
dotnet add package FlexQuery.NET.AspNetCore
Registration
builder.Services.AddFlexQuery();
builder.Services.AddFlexQuerySecurity();
// Or combine with MVC:
builder.Services.AddControllers()
.AddFlexQuerySecurity();
Quick Start
[ApiController]
[Route("api/[controller]")]
public class UsersController : ControllerBase
{
[HttpGet]
[FieldAccess(AllowedFields = new[] { "Id", "Name", "Email", "Status" },
MaxFieldDepth = 2)]
public async Task<IActionResult> GetUsers([FromQuery] FlexQueryParameters parameters)
{
// FieldAccess settings are automatically resolved from HttpContext.
var result = await _context.Users.FlexQueryAsync(parameters, HttpContext);
return Ok(result);
}
}
Features
[FieldAccess]Attribute — Declare Allowed, Blocked, Filterable, Sortable, Selectable, Groupable, Aggregatable fields per-endpointFieldAccessFilter— Action filter that applies attribute settings toQueryExecutionOptions- **
Automatic Security Resolution** — Extension method acceptingHttpContext` for automatic options resolution - Swagger Integration — Works with Swagger/Swashbuckle for API documentation
Related Packages
- FlexQuery.NET — Core query engine
- FlexQuery.NET.EntityFrameworkCore — EF Core execution
- FlexQuery.NET.Dapper — Dapper execution
Documentation
| 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.1.0)
- FlexQuery.NET.EntityFrameworkCore (>= 3.1.0)
-
net6.0
- FlexQuery.NET (>= 3.1.0)
- FlexQuery.NET.EntityFrameworkCore (>= 3.1.0)
-
net8.0
- FlexQuery.NET (>= 3.1.0)
- FlexQuery.NET.EntityFrameworkCore (>= 3.1.0)
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 | 40 | 6/26/2026 |
| 3.0.6 | 80 | 6/24/2026 |
| 3.0.5 | 86 | 6/24/2026 |
| 3.0.4 | 85 | 6/23/2026 |
| 3.0.3 | 89 | 6/23/2026 |
| 3.0.2 | 98 | 6/22/2026 |
| 3.0.1 | 100 | 6/22/2026 |
| 2.5.0 | 105 | 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 | 99 | 5/4/2026 |
| 1.7.1 | 98 | 5/2/2026 |
| 1.6.2 | 104 | 5/2/2026 |
# FlexQuery.NET v3.1.0 Release Notes
**Release date:** 2026-06-25
## Overview
v3.1.0 introduces query execution observability, AG Grid camelCase support, HAVING query pipeline fixes, a shared projection metadata layer unifying Dapper and EF Core serialization behavior, DynamicType materialization for GroupBy/aggregate results, and a new diagnostics API. The Dapper return type is unified with EF Core (`QueryResult<object>`).
---
## What's New
### 1. Execution Diagnostics / Observability
New `IFlexQueryExecutionListener` interface with 4 lifecycle events that let you observe the full query pipeline:
| Event | When | Data |
|-------|------|------|
| `QueryParsed` | After parsing input parameters | `QueryOptions`, elapsed time |
| `QueryTranslated` | After SQL generation | SQL string, parameters |
| `QueryExecuted` | After database query returns rows | Row count, optional exception |
| `QueryMaterialized` | After `QueryResult<object>` is built | Final result, optional exception |
Both Dapper and EF Core providers now accept an optional `configureExecution` callback on all overloads:
```csharp
var result = await connection.FlexQueryAsync<Customer>(parameters, opts,
exec => exec.Listener = new MyTelemetryListener());
```
New types:
- `FlexQueryExecutionConfig` — config class with `Listener` property
- `FlexQueryExecutionContext` — internal context carrying `QueryId`, `Stopwatch`, listener, and `CancellationToken`
- `FlexQueryExecutionEvent` base class with event data types `QueryParsedEvent`, `QueryTranslatedEvent`, `QueryExecutedEvent`, `QueryMaterializedEvent`
- `IFlexQueryExecutionListener` — 4 async methods for lifecycle hooks
### 2. AG Grid SSRM camelCase Support
`AgGridResponseConverter.Convert()` and `ToAgGridServerSideResponse()` extension methods accept a new `camelCase` parameter. When `true`, POCO property names in row data dictionaries are automatically converted from PascalCase to camelCase. Group metadata field names remain configurable via `AgGridResponseFieldOptions`.
### 3. DynamicType for GroupBy / Aggregate Results
GroupBy and aggregate queries now produce `DynamicType` instances instead of `Dictionary<string, object>`. DynamicType properties flow through ASP.NET Core's `PropertyNamingPolicy`, so camelCase serialization works automatically without manual conversion.
### 4. Shared Projection Metadata Layer
`ProjectionMetadataBuilder` extracted as a shared component used by both Dapper and EF Core providers. Reduces duplication and ensures consistent projection resolution (nested paths, field types, IEnumerable detection) across both pipeline implementations.
### 5. Sample Web API Project
New `FlexQuery.NET.Samples.WebApi` demonstrating EF Core, Dapper, AG Grid SSRM, and Kendo UI integrations with SQLite, Swagger, and demo frontends.
### 6. Benchmarks
- `DapperSqlGenerationBenchmarks` — simple, complex-filter, and aggregate SQL generation against SqlServer dialect
- `ProjectionBenchmarks` — `SelectTreeBuilder`, `DynamicTypeBuilder` cache hit/miss, `QueryCacheKeyBuilder` performance
---
## What's Fixed
### 1. HAVING Pipeline — Parser, SQL Generation, Alias Naming
Three independent bugs that caused HAVING clauses to be silently dropped or return incorrect results:
**a) HavingParser regex regression:**
The field portion of the HAVING expression was non-optional. Input like `count:gt:20` would misinterpret `gt` as a field name and return `null` because no field was found before the colon.
```csharp
// Before: field-less aggregate HAVING parsed as null
var having = parser.Parse("count:gt:20"); // null
// After:
var having = parser.Parse("count:gt:20"); // { Field: null, Aggregate: Count, Operator: GreaterThan, Value: 20 }
```
**b) Dapper SqlTranslator.BuildHavingClause:**
Field-less aggregates (`COUNT(*)`) emitted quoted `[*]` columns (`COUNT([*])`) and bound comparison values as strings (`'20'` instead of `20`). SQLite rejected `INTEGER > TEXT` comparisons, returning zero rows.
```sql
-- Before (broken for SQLite):
HAVING COUNT([*]) > '20'
-- After:
HAVING COUNT(*) > @p0 -- @p0 = 20 (int)
```
**c) BuildAggregateAlias:**
Field-less aggregates produced `allCount` as the alias instead of the predictable `Count`.
```sql
-- Before:
SELECT COUNT(*) AS [allCount]
-- After:
SELECT COUNT(*) AS [Count]
```
### 2. Dapper TotalCount / ResultCount Alignment with EF Core
**Before:** `ResultCount` was unconditionally set to `items.Count` (the page size), even when `IncludeTotalCount` was false.
**After:** `ResultCount` is only set when counts are explicitly enabled. Checks both `options.IncludeCount` (user-level) and `execOptions.IncludeTotalCount` (server-level). Both providers now return identical `QueryResult` structure for all scenarios.
### 3. Dapper Connection Lifecycle
Documented that `FlexQueryAsync<T>()` auto-opens the connection if closed but never auto-closes it. Added `CancellationToken` parameter to `ExecuteQueryAsync`.
---
## Breaking Changes
### 1. Dapper Return Type: `QueryResult<T>` → `QueryResult<object>`
**Affected package:** `FlexQuery.NET.Dapper`
**Before:** `FlexQueryAsync<T>()` returned `Task<QueryResult<T>>`.
**After:** Returns `Task<QueryResult<object>>`.
This unifies the Dapper provider's return type with the EF Core provider. Required because Dapper now uses `DynamicType` for projection materialization, and the projected type is not known at compile time.
**Migration:**
```csharp
// Before (will not compile):
QueryResult<Customer> result = await connection.FlexQueryAsync<Customer>(...);
// After:
QueryResult<object> result = await connection.FlexQueryAsync<Customer>(...);
// OR:
var result = await connection.FlexQueryAsync<Customer>(...);
```
### 2. DebugResult Namespace Change
**Affected package:** `FlexQuery.NET`
`DebugResult` moved from inline class in `FlexQuery.NET.Extensions` (inside `FlexQueryDebugExtensions.cs`) to `FlexQuery.NET.Models`.
**Migration:** Add `using FlexQuery.NET.Models;` where `DebugResult` is referenced.
---
## Behavioral Changes
| Change | Impact | Mitigation |
|--------|--------|------------|
| Dapper return type → `QueryResult<object>` | Compile break on explicit return type annotations | Use `var` or change to `QueryResult<object>` |
| `DebugResult` → `FlexQuery.NET.Models` | Compile break if relying on `FlexQuery.NET.Extensions` | Add `using FlexQuery.NET.Models;` |
| Dapper `ResultCount` null when count is off | Matches EF Core; previously returned `items.Count` | Add null check if needed |
| Field-less HAVING `Count` alias | Previously `allCount` | Update any HAVING filter referencing `allCount` |
| GroupBy/aggregate rows → `DynamicType` | Property names respect JSON naming policy; no longer `Dictionary<string, object>` | Access via properties instead of dictionary keys, or use reflection/DynamicType API |
---
## New Test Coverage
### HAVING Pipeline (19 new tests)
| Test | Area | Verifies |
|------|------|----------|
| `Parse_Having_FieldLessAggregate` | Parser | `count:gt:10` parses with null field |
| `Parse_Having_ParenthesizedValue` | Parser | `(count:gt:10)` parenthesized syntax |
| `Parse_Having_ColonSeparatedField` | Parser | Field-based HAVING with colons |
| `Parse_Having_Count` | Parser | `count` aggregate parsed |
| `Parse_Having_Sum` | Parser | `sum` aggregate parsed |
| `Parse_Having_Avg` | Parser | `avg` aggregate parsed |
| `Parse_Having_Max` | Parser | `max` aggregate parsed |
| `Parse_Having_Min` | Parser | `min` aggregate parsed |
| `BuildHavingClause_CountStar_NoField` | SQL generation | `COUNT(*) > @p0` without field |
| `BuildHavingClause_SumField` | SQL generation | `SUM([Amount]) > @p0` with field |
| `BuildHavingClause_AvgField` | SQL generation | `AVG([Amount]) > @p0` with field |
| `BuildHavingClause_MaxField` | SQL generation | `MAX([Amount]) > @p0` with field |
| `BuildHavingClause_MinField` | SQL generation | `MIN([Amount]) > @p0` with field |
| `BuildAggregateAlias_FieldLessAggregate_ReturnsCount` | Alias | Field-less → `Count` |
| `BuildAggregateAlias_FieldAggregate_ReturnsFieldFunction` | Alias | Field + function → `AmountSum` |
| `Having_Count_Grouped_Sorted_Paged` | Integration | Full pipeline: Count HAVING + sort + paging |
| `Having_Sum_Grouped_Sorted_Paged` | Integration | Full pipeline: Sum HAVING + sort + paging |
| `Having_Avg_Grouped_Projected` | Integration | HAVING with projection |
| `Having_Max_Min_Grouped_Projected` | Integration | Multiple aggregates HAVING with projection |
### GroupBy / Integration (204 new test lines)
Additional GroupBy execution tests across EF Core and Dapper covering dict vs DynamicType materialization, grouping with sort/paging combinations, and aggregate result structures.
### Parser (117 new test lines)
Additional parser coverage for edge cases and boundary conditions.
### AgGridResponseConverter
Adapted existing tests to cover the new `camelCase` parameter (both `true` and `false` paths).
---
## Additional Changes (post-commit)
- **ToQueryOptions extension method:** New `FlexQueryParametersExtensions.ToQueryOptions()` convenience extension on `FlexQueryParameters` for direct-to-`QueryOptions` parsing without calling `QueryOptionsParser.Parse()`.
- **Sample controllers simplified:** `DapperCustomersController` and `EfCustomersController` migrated to primary constructors and simplified to use the full FlexQueryAsync API instead of manual GroupBy handling.
- **MiniOData assembly name fix:** `QueryOptionsParser` registration corrected from `FlexQuery.NET.MiniOData` to `FlexQuery.NET.Parsers.MiniOData`.
- **Internal call sites updated:** `FlexQueryDapperExtensions`, `ProjectionEfCoreExtensions`, `QueryableEfCoreExtensions`, and `QueryableExtensions` all updated to use `parameters.ToQueryOptions()`.
---
## Migration Guide
### Dapper Return Type
Replace explicit `QueryResult<T>` with `QueryResult<object>` or use `var`:
```csharp
// Before:
QueryResult<Customer> result = await connection.FlexQueryAsync<Customer>(...);
// After:
var result = await connection.FlexQueryAsync<Customer>(...);
```
### HAVING Alias
If you reference the auto-generated alias for field-less aggregate HAVING filters, replace `allCount` with `Count`:
```csharp
// Before:
options.Having.Add(new HavingNode("allCount:gt:10"));
// After:
options.Having.Add(new HavingNode("Count:gt:10"));
```
### DebugResult Import
```csharp
// Add this using:
using FlexQuery.NET.Models;
```
---
## Upgrading
```bash
dotnet add package FlexQuery.NET --version 3.1.0
dotnet add package FlexQuery.NET.Dapper --version 3.1.0
dotnet add package FlexQuery.NET.EntityFrameworkCore --version 3.1.0
dotnet add package FlexQuery.NET.Parsers.Jql --version 3.1.0
dotnet add package FlexQuery.NET.Parsers.MiniOData --version 3.1.0
dotnet add package FlexQuery.NET.Adapters.AgGrid --version 3.1.0
dotnet add package FlexQuery.NET.Adapters.Kendo --version 3.1.0
dotnet add package FlexQuery.NET.AspNetCore --version 3.1.0
```