Pagination¶
The provider exposes DynamoDB's cursor-based continuation model through ToPageAsync and WithNextToken; there are no integer offsets — pagination is always driven by opaque tokens returned by DynamoDB.
Continuation Tokens¶
DynamoDB returns an opaque NextToken string when a response was cut short by the evaluation budget (Limit) or the 1 MB per-request data cap. A non-null NextToken means more results may exist; NextToken == null means the result set is exhausted.
Tokens are pass-through values — the provider does not parse or manipulate them. Empty or whitespace token strings are normalized to null. You can store a token and use it later to resume the query from that position.
Note
End of results is always `NextToken == null`. Do not use `Items.Count < limit` as a termination condition — a page can legitimately return zero items while still having a non-null `NextToken` when all evaluated items were filtered out by a non-key `Where` predicate.
ToPageAsync¶
ToPageAsync(limit, nextToken, cancellationToken) executes exactly one DynamoDB request and returns a DynamoPage<T>. The page contains:
Items— the matching results from this request.NextToken— the continuation cursor for the next page, ornullat end of results.HasMoreResults— shorthand forNextToken != null.
limit is the evaluation budget for this request, not a guarantee of how many items are returned. A page may return fewer than limit items (including zero) and still have a non-null NextToken if filtered items exhausted the budget.
// Canonical pagination loop
string? token = null;
do
{
var page = await db.Orders
.Where(o => o.CustomerId == customerId)
.ToPageAsync(25, token, cancellationToken);
foreach (var order in page.Items)
{
// process order
}
token = page.NextToken;
}
while (token != null);
ToPageAsync cannot be combined with .Limit(n) on the same query — the limit is supplied as the first argument to ToPageAsync itself.
WithNextToken¶
.WithNextToken(token) seeds the first request of the query at a previously saved cursor. Subsequent requests (when the provider follows continuation automatically) use server-returned tokens as usual.
// Resume full enumeration from a saved token
var remaining = await db.Orders
.Where(o => o.Status == "PENDING")
.WithNextToken(savedToken)
.ToListAsync(cancellationToken);
// Fetch one bounded chunk from a saved token
var chunk = await db.Orders
.Where(o => o.Status == "PENDING")
.WithNextToken(savedToken)
.Limit(25)
.ToListAsync(cancellationToken);
// Single paged request from a saved token
var page = await db.Orders
.Where(o => o.Status == "PENDING")
.WithNextToken(savedToken)
.ToPageAsync(25, null, cancellationToken);
WithNextToken accepts only a non-null, non-whitespace string. It can be applied once per query. When combined with ToPageAsync(limit, nextToken), only one of the two token sources may be non-null; providing a non-null token to both throws at execution.
WithNextToken is not supported with First/FirstOrDefault or Single/SingleOrDefault query shapes.
Page Size vs Evaluation Budget¶
The evaluation budget and the number of returned items are separate concerns:
- The budget (
limitinToPageAsync, orninLimit(n)) controls how many items DynamoDB reads and evaluates filter expressions against. - The number of items in
page.Itemsis how many of those evaluated items matched theWHEREpredicate.
With a highly selective filter, DynamoDB may evaluate the entire budget without returning any matching items — yet still set NextToken because items beyond the budget position were not checked. Keep paginating until NextToken == null.
Note
There is no global default page size. Specify the evaluation budget explicitly on each `ToPageAsync` call or via `Limit(n)`.
Evaluation Budget Reference¶
| Query Shape | ExecuteStatementRequest.Limit |
Pages? |
|---|---|---|
.Limit(n) + ToListAsync() |
n |
No (single request) |
.ToPageAsync(n, token) |
n |
One request |
.WithNextToken(token) + ToListAsync() |
null (1 MB/page) |
Yes |
.WithNextToken(token) + .Limit(n) + ToListAsync() |
n |
No (single request) |
.WithNextToken(token) + .ToPageAsync(n, null) |
n |
One request |
First* (key-only, no explicit limit) |
1 |
No |
First* + any Limit(n) |
Translation failure | — |
.Limit(n) + AsAsyncEnumerable() + First* |
n (client-side selection) |
No |
Single* (key-condition-only, no explicit limit) |
2 |
No; any NextToken throws guard |
.WithNextToken(token) + Single* |
Translation failure | — |
Single* + any Limit(n) |
Translation failure | — |
.Limit(n) + AsAsyncEnumerable() + Single* |
n (client-side selection) |
No |
ToListAsync() (no limit) |
null (1 MB/page) |
Yes |
Accessing the Raw Response Token¶
For Limit(n) + ToListAsync() queries, the provider does not follow NextToken automatically — the query is a single request. If you want to continue from where the evaluation budget stopped, retrieve the response token from a tracked entity entry and use it to seed the next query.
This pattern requires that at least one item is materialized, because response metadata is attached to entity entries:
var items = await db.Orders
.Where(o => o.Status == "ACTIVE")
.Limit(25)
.ToListAsync(cancellationToken);
string? nextCursor = null;
// Cursor at the end of this evaluation range (when at least one entity was returned)
if (items.Count > 0)
{
nextCursor = db.Entry(items[0]).GetExecuteStatementResponse()?.NextToken;
}
// Resume with WithNextToken on a subsequent query
if (nextCursor != null)
{
var nextChunk = await db.Orders
.Where(o => o.Status == "ACTIVE")
.WithNextToken(nextCursor)
.Limit(25)
.ToListAsync(cancellationToken);
}
Note
If you need a continuation token even when a page returns zero items, use `ToPageAsync(limit, token)` instead. `ToPageAsync` always returns `DynamoPage<T>.NextToken`, while entry metadata is only available when at least one tracked entity is materialized.
All items from the same request share the same response object reference. For ToListAsync() without Limit, the provider follows all continuation tokens internally; by the time ToListAsync returns, those tokens have already been consumed. Use ToPageAsync when you need explicit cursor control across requests.