Skip to content
FlexQuery.NET

Query Result

Every execution method returns QueryResult<T> — a uniform envelope that pairs the page of data with the metadata clients need for pagination UIs, aggregate displays, and cursor-based navigation. Because the envelope is the same for EF Core and Dapper, dynamic and typed results, your response contract never changes when the query does.

Properties

PropertyTypeDescription
DataIReadOnlyList<T>The page of results (entities, projected objects, or DTOs).
TotalCountint?Source rows matching the query before paging — independent of what is returned in Data. Null when counting is disabled. On grouped queries it is the number of underlying rows, not the number of groups.
ResultCountint?The post-shaping row total (groups for grouped queries, distinct rows for distinct) and what TotalPages is computed from. Null on plain queries unless the provider computes it.
PageintCurrent 1-based page number.
PageSizeintEffective page size (after clamping).
TotalPagesintComputed from total count and page size.
HasNextPageboolA next page exists.
HasPreviousPageboolA previous page exists.
AggregatesDictionary<string, Dictionary<string, object>>?Grand totals for ungrouped aggregate queries: field → aggregate key → value. Null otherwise.
NextCursorTokenstring?Cursor for the next keyset page (keyset mode only).
ResultShapeIReadOnlyList<SelectOutputField>?The effective output surface when an explicit select is present.

ResultShape fields

Each SelectOutputField describes one output column:

FieldMeaning
SourceNameThe public/source field the client requested (e.g. CustomerFullName).
SourcePropertyNameThe entity property it resolves to.
OutputNameThe response field name — the alias when present, otherwise the source name.

When the result-shape JSON converter is registered (AddFlexQuerySecurity() / AddFlexQueryJson()), serialization enforces exactly this surface: anything outside it is stripped from the payload, with aliases applied.

Serialization example

JSON
{
  "data": [ /* 20 rows */ ],
  "totalCount": 137,
  "page": 3,
  "pageSize": 20,
  "totalPages": 7,
  "hasNextPage": true,
  "hasPreviousPage": true
}

(The resultCount key is only present when a result-shape count was computed — grouped or distinct queries.)

Clients can build complete pagination UIs from this envelope alone: page numbers (totalPages), next/previous buttons (hasNextPage/hasPreviousPage), and row counts (totalCount).

Grouped queries

Grouped queries produce one Data entry per group. Each row carries the group key(s) plus every aggregate under its alias, and the paging metadata is computed over groups: totalCount remains the underlying source-row count, resultCount is the number of groups, and totalPages/hasNextPage follow from the group count.

JSON
{
  "data": [
    { "status": "Active", "sumTotal": 1250.00, "countOrders": 14 }
  ],
  "totalCount": 417,
  "resultCount": 1,
  "page": 1,
  "pageSize": 20,
  "totalPages": 1,
  "hasNextPage": false,
  "hasPreviousPage": false
}

Ungrouped aggregate queries (?aggregate=... without groupBy) do not produce group rows; their single-row totals appear in a separate aggregates object keyed by the aggregate's source field (or "all"), with inner entries keyed by alias:

JSON
{
  "data": [ ...paged matching records... ],
  "aggregates": { "TotalAmount": { "sumRevenue": 1250.00 } },
  "totalCount": 417,
  "page": 1,
  "pageSize": 20
}

See Grouping & Aggregates for the full semantics.

Keyset pagination

When keyset mode is active, NextCursorToken carries the opaque, versioned cursor built from the sort-key values of the last row on the page. When a page comes back empty, the token is null — the standard end-of-scroll signal. Pass it back as the cursor parameter — see Keyset Pagination.

Complete worked example

Shaping a stable public response from the envelope:

C#
var result = await db.Customers
    .FlexQueryAsync(parameters, cancellationToken: cancellationToken);

return Ok(new
{
    items = result.Data,
    pagination = new
    {
        page = result.Page,
        pageSize = result.PageSize,
        total = result.TotalCount,
        totalPages = result.TotalPages,
    },
    nextCursor = result.NextCursorToken,
});

Common mistakes