MongoDB Reads in C#: Projection, Sorting, and Pagination
A correct filter is not enough: a MongoDB read can still return unnecessary fields, an undefined order or an unbounded amount of data. Nick Cosentino walks through typed projection, sorting with a unique tie-breaker, offset pagination and compound continuation tokens on MongoDB.Driver 3.12.0. The guide skips basic filter syntax and answers the follow-up questions instead: what comes back, in which order, and how a later request continues.
Key points
- Projection is part of the read contract: select the boundary fields (Id, Sequence) you need to continue, not just the displayed ones
- Add a unique tie-breaker (_id) to the sort and repeat both directions in the continuation predicate
- Skip/Limit offsets still process earlier results as the page number grows and shift when documents are inserted or deleted
- A narrow projection does not prove index coverage; check the actual query plan against the deployed indexes
- A continuation token only means something together with its filter, sort mode and value types; combine it with the customer predicate, never replace it
- A limit bounds returned documents, not the keys or documents the server examines
Past the filter, a MongoDB read has three separate pieces: what returns, in which order, and how much. In MongoDB.Driver 3.12.0 the expression overload of Project returns IFindFluent<TDocument, TNewProjection>, so a summary type can stand in for the stored document type. That partial object should never be reused as a write model, a small anonymous shape proves no index coverage, and missing projected members may silently arrive as CLR defaults.
Sorting needs a unique tie-breaker: descending Sequence alone leaves equal values unordered, so the example pairs it with descending _id and repeats both directions in the continuation predicate. Offset pagination is easy to explain to a paged UI, but the server still walks earlier results and pages shift when documents are inserted, so separate requests never form one snapshot.
Real-world use
- An order history screen with numbered pages backed by a validated Skip/Limit read
- An infinite-scroll list over large customer collections using a compound boundary and continuation token
- A dashboard endpoint that returns summaries via typed projection instead of whole documents
- A high-volume background job that iterates a cursor in batches instead of materializing with ToListAsync
Why it matters
A correct filter alone does not make a correct endpoint; without projection, a unique sort and bounded pagination, the API returns fields nobody needs and pages that skip or duplicate rows.