Summary
A filtered/ordered collection Include with more than one OrderBy/ThenBy key silently returns rows sorted by only the last key instead of a stable multi-key sort. Separately, an OrderBy/ThenBy key selector that isn't a simple property access silently sorts by _id instead of failing translation.
Location
src/MongoDB.EntityFrameworkCore/Query/Visitors/MongoProjectionBindingExpressionVisitor.Lookup.cs:
- ExtractFilteredIncludePipeline (around lines 669-735) — each OrderBy/ThenBy/OrderByDescending/ThenByDescending call emits its own separate {{
Unknown macro: { "$sort"}
}} pipeline stage (lines 683-690).
- GetSortField (around lines 803-814) — returns the literal string "_id" when the key selector isn't recognized as a simple property access (line 809), instead of throwing.
Root cause
- Multi-key sort collapse. MongoDB's $sort stage does not accumulate across multiple sequential $sort stages — a later $sort stage fully overrides the ordering established by an earlier one, it does not add a secondary sort key. Include(c => c.Orders.OrderBy(o => o.CustomerId).ThenBy(o => o.OrderDate)) therefore emits two separate $sort stages and the pipeline effectively sorts only by OrderDate, discarding the primary key entirely — silently wrong order, no exception.
- Fix: merge all OrderBy/ThenBy keys collected for one filtered-Include chain into a single $sort stage with multiple fields ({{
Unknown macro: { "$sort"}
}}), matching how the top-level (non-Include) OrderBy/ThenBy translation already merges keys.
- Fix: merge all OrderBy/ThenBy keys collected for one filtered-Include chain into a single $sort stage with multiple fields ({{
- Silent _id fallback. When the key selector body isn't a simple property access (e.g. a computed expression, a value-converted property needing translation, or any shape TryGetSimplePropertyName doesn't recognize), GetSortField returns "_id" rather than surfacing the unsupported shape. This produces a plausible-looking but wrong sort order with no error, rather than a clear translation-failure the caller could act on.
- Fix: throw (e.g. via CoreStrings.TranslationFailed) instead of returning "_id", consistent with how ExtractFilteredIncludePipeline's own Where branch already fails loudly for unsupported predicate shapes rather than guessing.
Impact
Silent wrong data (wrong element order within an included collection) with no exception — same class of defect as the already-fixed filtered-Include Where-drop issue (EF-367/EF-X021), but for the sort clause. Severity is lower than a data-disclosure bug (no cross-tenant leak, no wrong set of rows — only wrong order of an already-correctly-scoped collection), but it is silent and will not show up as a test failure unless the test specifically asserts row order after a multi-key OrderBy/ThenBy inside a filtered Include.
Notes
- Found while re-triaging the now-deleted docs/include-implementation-review.md (originally written against branch
EF-117k) against current main and the currently-open PR queue. Most of that document's findings are already fixed on main or covered by open PRs (EF-371/372/374-381 etc.); the composite-key-truncation finding from the same document is already tracked separately as EF-385. This ticket is the one review finding (its "M3") with no existing fix or ticket. - Confirmed against current main (commit e7c4d0b5) by reading the cited lines directly — not from the historical line numbers in the old doc.