You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Cosmos: ToPageAsync silently returns full result set with null continuation token when query contains ORDER BY (against the Cosmos Emulator vnext-preview) #38163
Cosmos: ToPageAsync silently returns full result set with null continuation token when query contains ORDER BY (against the Cosmos Emulator)
Summary
When ToPageAsync(pageSize, continuationToken) is called on a Cosmos-backed query
that includes an OrderBy(...) / OrderByDescending(...) clause, the call
returns the entire result set (capped only by the partition size) with a null continuation token, instead of returning at most pageSize items with a
resumable token.
Removing the ORDER BY from the same query makes ToPageAsync behave as
documented (returns pageSize items + a non-null continuation token).
The base query is fully translatable — query.Take(pageSize).ToListAsync() on
the same IQueryable returns the requested number of rows correctly.
The failure is silent: no exception, no warning, no log entry. Callers see a
single page that contains far more rows than they asked for, then conclude the
result is exhausted because the continuation token is null.
This was reproduced against the mcr.microsoft.com/cosmosdb/linux/azure-cosmos-emulator:vnext-preview
image. I have not yet verified against production Cosmos DB.
cross-partition (no ORDER BY) ToPageAsync(10, null)
10 ✅
set
E
Where(GroupId == X) (no ORDER BY) ToPageAsync(10, null)
10 ✅
set
The presence of OrderBy / OrderByDescending is the precise trigger. The
partition-key shape is incidental: the bug fires equally on single- and
cross-partition variants of the ordered query.
Generated SQL (case B)
SELECT VALUE c
FROM root c
ORDER BY c["CreationTime"] DESC
The query is plainly translatable, and query.Take(10).ToListAsync() on the
identical IQueryable returns 10 rows, so the issue is specific to ToPageAsync's page-sizing behavior — not to query translation.
Real-world impact
We hit this in a Blazor Server app using EF Core's ToPageAsync to drive a
Telerik grid backed by a Cosmos container of ~500 documents. Symptom in
production-shaped UI: grid pager shows page size 10, but the OnRead callback
receives 500 items, the grid renders an empty page (the slice for "page 1 of 10
items" lands outside the actual returned data), and total-count display says
"1–501 of about 501 items". 5-second hang per refresh as the full container is
materialized and shipped over SignalR.
We worked around it by detecting the local/emulator environment and falling
back to Skip().Take() instead of ToPageAsync.
Notes
ToPageAsync is currently marked experimental (warning EF9102).
I'm aware of Holistic end-to-end pagination feature #33160 (holistic end-to-end pagination feature). Filing this as
a discrete report so the silent-misbehavior shape is on record regardless of
whether it's eventually fixed in the current ToPageAsync or rolled into the
broader pagination redesign — the silent failure mode (no exception, null
token implying exhausted result set) is the most dangerous part for users
who don't notice the row count is off.
Cosmos:
ToPageAsyncsilently returns full result set withnullcontinuation token when query containsORDER BY(against the Cosmos Emulator)Summary
When
ToPageAsync(pageSize, continuationToken)is called on a Cosmos-backed querythat includes an
OrderBy(...)/OrderByDescending(...)clause, the callreturns the entire result set (capped only by the partition size) with a
nullcontinuation token, instead of returning at mostpageSizeitems with aresumable token.
Removing the
ORDER BYfrom the same query makesToPageAsyncbehave asdocumented (returns
pageSizeitems + a non-null continuation token).The base query is fully translatable —
query.Take(pageSize).ToListAsync()onthe same
IQueryablereturns the requested number of rows correctly.The failure is silent: no exception, no warning, no log entry. Callers see a
single page that contains far more rows than they asked for, then conclude the
result is exhausted because the continuation token is
null.This was reproduced against the
mcr.microsoft.com/cosmosdb/linux/azure-cosmos-emulator:vnext-previewimage. I have not yet verified against production Cosmos DB.
Versions
Microsoft.EntityFrameworkCore.Cosmos)Microsoft.Azure.Cosmos(transitive)mcr.microsoft.com/cosmosdb/linux/azure-cosmos-emulator:vnext-previewRepro
Full self-contained repro: https://gist.github.com/jhulbertpmn/d367ee88ca4967d1802f7cea99e46a83
Run the emulator first:
Expected behavior
ToPageAsync(10, null)returns at most10items and a non-null continuationtoken (since 500 > 10).
Actual behavior
ToPageAsync(10, null)returns500items (the entire container) andcontinuationToken == null.Trigger isolation
Five permutations of the same data set, all with
pageSize = 10:OrderBy(CreationTime).Take(10).ToListAsync()OrderBy(CreationTime).ToPageAsync(10, null)Where(GroupId == X).OrderBy(CreationTime).ToPageAsync(10, null)ToPageAsync(10, null)Where(GroupId == X)(no ORDER BY)ToPageAsync(10, null)The presence of
OrderBy/OrderByDescendingis the precise trigger. Thepartition-key shape is incidental: the bug fires equally on single- and
cross-partition variants of the ordered query.
Generated SQL (case B)
The query is plainly translatable, and
query.Take(10).ToListAsync()on theidentical
IQueryablereturns 10 rows, so the issue is specific toToPageAsync's page-sizing behavior — not to query translation.Real-world impact
We hit this in a Blazor Server app using EF Core's
ToPageAsyncto drive aTelerik grid backed by a Cosmos container of ~500 documents. Symptom in
production-shaped UI: grid pager shows page size 10, but the OnRead callback
receives 500 items, the grid renders an empty page (the slice for "page 1 of 10
items" lands outside the actual returned data), and total-count display says
"1–501 of about 501 items". 5-second hang per refresh as the full container is
materialized and shipped over SignalR.
We worked around it by detecting the local/emulator environment and falling
back to
Skip().Take()instead ofToPageAsync.Notes
ToPageAsyncis currently marked experimental (warningEF9102).a discrete report so the silent-misbehavior shape is on record regardless of
whether it's eventually fixed in the current
ToPageAsyncor rolled into thebroader pagination redesign — the silent failure mode (no exception, null
token implying exhausted result set) is the most dangerous part for users
who don't notice the row count is off.
400 Bad Requeston subsequentcross-partition + ORDER BY pages.
continuation tokens at all.