Skip to content
FlexQuery.NET

Execution Pipeline

Every FlexQuery call — whether it started as a query string, a fluent build, or an adapter request — flows through the same five-stage pipeline. Knowing the stages, their order, and what each one guarantees is what lets you predict result ordering, interpret validation errors, and place custom logic at the right point.

The stages

Plain text
Query string / request model / fluent build
        |
        v
+------------+   1. Parse      - parameters -> QueryOptions (syntax-specific parser)
|   Parsers  |
+------------+
        |
        v
+------------+   2. Validate   - field existence, operators, types, field access,
| Validation |                    governance limits, expand paths, aggregates
+------------+
        |
        v
+------------+   3. Apply      - filter -> grouping/aggregates -> sort -> paging ->
|   Builder  |                    projection, composed as expression trees
+------------+
        |
        v
+------------+   4. Translate  - provider converts expressions to SQL (EF Core)
|  Provider  |                    or generates SQL directly (Dapper)
+------------+
        |
        v
+------------+   5. Execute    - query runs server-side; results materialize
|  Execution |                    into QueryResult<T>
+------------+

1. Parse

The syntax selected for the request (global default or per-request override) determines which parser runs. All parsers produce the same canonical QueryOptions — the rest of the pipeline is syntax-agnostic. Grammar failures surface as QueryParseException (which carries the offending parameter name, syntax, received value, and position), with the syntax-specific parse error as the inner exception — all deriving FlexQueryException.

2. Validate

The rule pipeline checks the parsed options against the entity model and governance configuration. Validation runs before any expression is built, so a rejected request costs no database work. See Validation.

3. Apply

The builder composes LINQ expressions in a fixed order:

  1. Filter (WHERE) — narrows rows first; everything downstream operates on fewer rows.
  2. GroupBy / Aggregates / Having — grouping forms after filtering; HAVING prunes groups before ordering.
  3. Sort (ORDER BY) — orders rows (or groups).
  4. Paging (OFFSET/FETCH or keyset seek predicates) — slices from the ordered set.
  5. Projection (SELECT) — last, so only requested fields materialize.
  6. Total count — computed on the filtered set, independent of paging and projection.

4. Translate

  • EF Core: the composed expression tree hands off to EF's translation — everything becomes SQL. Include/expand branches use EF Core filtered includes (.Include(...) expressions with Where/OrderBy/Take inside), so the related-data window is applied server-side in EF's own generated SQL.
  • Dapper: FlexQuery generates the SQL itself — select list (surface-aware, type-map rewritten), WHERE, GROUP BY/HAVING, ORDER BY, and dialect-specific paging. Related data loads as separate child queries batched by parent keys (split-query style hydration), and expand take becomes a server-side ranked/limited child query.

5. Execute

The provider executes; results materialize into QueryResult<T> with paging metadata, optional aggregates, and the optional cursor token. Cancellation is observed across the async overloads (see provider notes for scope).

Events

Each stage emits an event that any IFlexQueryExecutionListener can observe:

HookFired when
QueryParsedAsyncParameters parsed into QueryOptions.
QueryTranslatedAsyncProvider translated the query (SQL available).
QueryExecutedAsyncDatabase command completed (includes timing).
QueryMaterializedAsyncResults materialized into the result shape.

Attaching a listener is a one-liner (opt.Listener = ...), and FlexQueryDiagnosticsCollector accumulates all four into a report — see Diagnostics.

Where client code fits

  • Before parse — authentication, rate limiting.
  • Between parse and execute — governance via the configure delegate, tenant scoping by wrapping the IQueryable before FlexQuery sees it.
  • After execute — serialization (result-shape enforcement), diagnostics, response shaping.