Skip to content

How Queries Execute

The provider compiles LINQ expressions into PartiQL SELECT statements and executes them using DynamoDB's ExecuteStatement API, with any unsupported operators failing at translation time rather than silently falling back to client evaluation.

LINQ to PartiQL

When you execute a LINQ query, the provider runs it through a multi-stage compilation pipeline before any network call is made:

  1. DynamoQueryableMethodTranslatingExpressionVisitor walks the LINQ expression tree and builds a SelectExpression — an internal query model representing the projected columns, WHERE predicate, ORDER BY orderings, and evaluation limit.
  2. DynamoQueryTranslationPostprocessor finalizes the SelectExpression: it injects discriminator predicates for shared-table entity types and runs index selection analysis to determine whether a GSI or LSI should be used.
  3. DynamoQuerySqlGenerator converts the SelectExpression into a PartiQL SELECT statement with positional ? placeholders and a matching AttributeValue parameter list.

FindAsync uses EF Core's normal entity-finder path. It checks the change tracker first; if the entity is not tracked, EF Core produces a primary-key equality query that the provider translates through the same async PartiQL execution pipeline.

var orders = await db.Orders
    .Where(o => o.CustomerId == customerId && o.Status == "PENDING")
    .OrderBy(o => o.CreatedAt)
    .Select(o => new { o.OrderId, o.Total })
    .ToListAsync(cancellationToken);

// Generated PartiQL:
// SELECT "OrderId", "Total"
// FROM "Orders"
// WHERE "CustomerId" = ? AND "Status" = ?
// ORDER BY "CreatedAt" ASC

Identifiers are always double-quoted in generated PartiQL, and quoted identifiers are case-sensitive. The parameter values are serialized as AttributeValue objects; no literal values appear in the SQL text.

The ExecuteStatement Model

All queries execute via the DynamoDB ExecuteStatement API — not via the Query or Scan SDK methods. ExecuteStatement accepts a PartiQL SELECT and a list of positional parameters, then returns a page of results and an optional continuation token.

Two fields on the request control result size:

  • Limit — an evaluation budget: the number of items DynamoDB reads before applying filter expressions. It is not a guarantee of how many matching items are returned. A Limit(25) query may return anywhere from 0 to 25 items depending on how many of the evaluated items satisfy the WHERE predicate.
  • NextToken — a continuation cursor returned by DynamoDB when the response was cut short by the Limit or by the 1 MB processed-data cap per request. The provider follows NextToken automatically for unbounded queries; for paged queries it exposes the token via ToPageAsync.

Note

DynamoDB stops processing a request at either the `Limit` item count or 1 MB of processed data, whichever comes first. A page can therefore contain zero matching items while still returning a non-null `NextToken` when many non-matching items were evaluated. Always check `NextToken == null` (not `Items.Count`) to determine end of results.

Client-Side vs Server-Side Evaluation

The provider has a strict server-side-first policy: if a LINQ operator cannot be translated to PartiQL, the translation fails with an InvalidOperationException at compile time. The provider never silently evaluates unsupported operators in-process against a full table scan.

The one sanctioned form of client-side evaluation is explicit: calling .AsAsyncEnumerable() marks the boundary between server execution and LINQ-to-objects. Any LINQ operators chained after AsAsyncEnumerable() run in-process against the result stream the server returned.

// Server evaluates the WHERE and Limit; client selects the first match.
var active = await db.Orders
    .Where(o => o.CustomerId == customerId && o.IsActive)
    .Limit(50)
    .AsAsyncEnumerable()
    .FirstOrDefaultAsync(cancellationToken);

Some projection shaping also runs client-side. Computed expressions in Select (string methods, arithmetic, constructor calls) and nested complex-property materialization are applied by the shaper lambda after the server returns the raw AttributeValue rows. This is expected behavior and is documented per operator on the Projection page.

Debugging Generated PartiQL

Use EF Core's ToQueryString() to inspect generated PartiQL without sending a request to DynamoDB. Parameterized queries include comment lines with positional parameter names (p0, p1, ...) and formatted DynamoDB AttributeValue values before the PartiQL text.

var partiQl = db.Orders
    .Where(o => o.CustomerId == customerId)
    .ToQueryString();

// -- p0='CUSTOMER#123'
// SELECT "pk", "sk", "total"
// FROM "Orders"
// WHERE "CustomerId" = ?

ToQueryString() is for debugging only: it does not run scan warnings, log query execution events, or execute ExecuteStatement.

Async Execution

All DynamoDB query execution is async. Attempting to enumerate results synchronously throws InvalidOperationException. Find() can return an already-tracked entity without executing a query; use FindAsync(), ToListAsync(), FirstOrDefaultAsync(), AsAsyncEnumerable(), or ToPageAsync() when DynamoDB I/O is needed.

See also