FlexQuery.NET.Parsers.Jql 3.1.0

dotnet add package FlexQuery.NET.Parsers.Jql --version 3.1.0
                    
NuGet\Install-Package FlexQuery.NET.Parsers.Jql -Version 3.1.0
                    
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.Parsers.Jql" Version="3.1.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="FlexQuery.NET.Parsers.Jql" Version="3.1.0" />
                    
Directory.Packages.props
<PackageReference Include="FlexQuery.NET.Parsers.Jql" />
                    
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.Parsers.Jql --version 3.1.0
                    
#r "nuget: FlexQuery.NET.Parsers.Jql, 3.1.0"
                    
#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.Parsers.Jql@3.1.0
                    
#: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.Parsers.Jql&version=3.1.0
                    
Install as a Cake Addin
#tool nuget:?package=FlexQuery.NET.Parsers.Jql&version=3.1.0
                    
Install as a Cake Tool

FlexQuery.NET.Parsers.Jql

NuGet Version

JQL (Jira Query Language) parser for FlexQuery.NET.

When to Use This Package

Install this package when you want to support JQL-style filter syntax in your API. The JQL parser integrates with the QueryOptionsParser pipeline so JQL queries are automatically detected and parsed alongside the native DSL format.

Installation

dotnet add package FlexQuery.NET.Parsers.Jql

Quick Start

using FlexQuery.NET.Parsers.Jql;

var parser = new JqlQueryParser();
var filterGroup = parser.Parse("Status = 'Active' AND Age >= 18");

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

JQL Syntax Examples

Status = 'Active'
Age >= 18 AND
Name CONTAINS 'john'
Status = 'Active' AND Age >= 18
Category = 'Electronics' OR Category = 'Books'
DeletedAt IS NULL
Status IN ('Active', 'Pending')

Features

  • JqlQueryParser — Parses JQL filter expressions into FilterGroup AST
  • Auto-Detection — Registers as IQueryParser so JQL queries are handled seamlessly
  • Supported Operators — eq, neq, gt, gte, lt, lte, contains, startswith, endswith, like, isnull, isnotnull, in, notin, between, any, all, count
  • Standalone Usage — Can be used without the full FlexQuery execution pipeline

Known Limitations

  • The parser implements a subset of the full JQL specification — complex Jira functions and custom fields are not supported
  • Date/time parsing uses .NET conventions rather than Jira-specific formats

Documentation

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 0 6/26/2026
3.0.6 46 6/24/2026
3.0.5 47 6/24/2026
3.0.4 45 6/23/2026
3.0.3 49 6/23/2026
3.0.2 94 6/22/2026
3.0.1 98 6/22/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
```