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:
DynamoQueryableMethodTranslatingExpressionVisitorwalks the LINQ expression tree and builds aSelectExpression— an internal query model representing the projected columns,WHEREpredicate,ORDER BYorderings, and evaluation limit.DynamoQueryTranslationPostprocessorfinalizes theSelectExpression: it applies discriminator predicates when shared-table, inheritance, or multi-concrete table groups need type filtering, and runs index selection analysis to determine whether a GSI or LSI should be used.DynamoQuerySqlGeneratorconverts theSelectExpressioninto a PartiQLSELECTstatement with positional?placeholders and a matchingAttributeValueparameter 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. ALimit(25)query may return anywhere from 0 to 25 items depending on how many of the evaluated items satisfy theWHEREpredicate.NextToken— a continuation cursor returned by DynamoDB when the response was cut short by theLimitor by the 1 MB processed-data cap per request. The provider followsNextTokenautomatically for unbounded queries; for paged queries it exposes the token viaToPageAsync.
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", "$type", "total"
// FROM "Orders"
// WHERE "CustomerId" = ?
ToQueryString() is for debugging only: it does not run scan warnings, log query execution events,
or execute ExecuteStatement.
Asynchronous execution¶
All DynamoDB query execution is async. The AWS SDK exposes no synchronous statement execution, so
the provider is async-only: attempting to enumerate query results synchronously throws
InvalidOperationException with guidance to use ToListAsync, FirstAsync, or
AsAsyncEnumerable(). Non-executing APIs such as ToQueryString() keep working.
Precompiled queries¶
When EF Core query precompilation is enabled, EF Core generates interceptors for query calls during
the build. The provider stores the translated PartiQL as a compact command template in those
interceptors. At runtime it supplies scalar and collection parameter values, creates DynamoDB
AttributeValue parameters, and executes the same query pipeline used by normal compiled queries.
The generated materializer resolves each projected property through the compiled EF model. This preserves property-specific value converters without serializing the provider's internal query expression tree into generated source.
See Precompiled Queries and NativeAOT for project setup and current restrictions, including the tested NativeAOT model limitations.
Async execution¶
All database queries use async methods such as ToListAsync() and FirstOrDefaultAsync(). This
also applies to generated precompiled queries. Synchronous query operators and enumeration throw
InvalidOperationException. Find() can return an already-tracked entity without executing a
query. Use FindAsync() for database lookups and ToPageAsync() when you need explicit
continuation-token control.