Skip to content

Order collection navigations loaded by a Select projection - #184

Merged
SimonCropp merged 1 commit into
mainfrom
order-projected-collections
Nov 4, 2026
Merged

SimonCropp merged 1 commit into
mainfrom
order-projected-collections

Conversation

@SimonCropp

@SimonCropp SimonCropp commented Nov 4, 2026 •

Copy link
Copy Markdown
Owner

Collection navigations read inside a Select projection are now ordered by their entity's default ordering, the same as collections in an Include().

A projected collection, such as Select(_ => new { Employees = _.Employees.Where(...).Select(...).ToList() }), is loaded by a subquery of its own. Nothing ordered it, so EF Core returned its rows in key order and the configured default was ignored. GraphQL.EntityFramework loads navigation list fields this way, so a client that left out orderBy because it matched the model's default got key order instead, which for a Guid key looks random.

ProjectionOrderingApplicator visits the lambdas of Queryable.Select and orders each unordered collection navigation it finds:

  • The ordering goes after any Where and before Select, Skip, Take, First, FirstOrDefault, Last, LastOrDefault, ElementAt, ElementAtOrDefault, ToList, ToArray and AsEnumerable
  • A navigation bound into the result as it is, such as new { _.Employees }, is ordered and copied into a list, where a list fits the member's type
  • Nested collections each get their own entity's default
  • A collection only aggregated (Count, Any, Sum and the like), or composed with an operator EF Core drops ordering beneath such as Distinct, is left alone, as is the second operand of a set operation
  • JSON mapped collections keep their document order, as for Include()
  • Explicit ordering inside a projection is kept, and redundant ordering detection checks it the same way as ordering inside an Include()

This changes the SQL of any query that projects a collection navigation without ordering it: the subquery gains the default ORDER BY. With throwOnRedundantOrderBy on, an explicit ordering in a projection that matches the default now throws.

Tests cover each case in ProjectionOrderingTests, and redundant detection in projections in RedundantOrderByTests. One existing snapshot changed: DefaultOrdering_WithIncludeAndSelect_WithNavInSelect, whose bound _.Employees now gets HireDate DESC.

Documented in the readme. Version bumped to 1.4.0.

GraphQL.EntityFramework orders projected navigation lists by key unless told not to. SimonCropp/GraphQL.EntityFramework#1458 adds the option that lets this default apply instead.

A collection navigation read inside a Select projection is loaded by a
subquery of its own. Nothing ordered it, so EF Core returned its rows in key
order and the configured default ordering was ignored. Only Include()
collections got it.

ProjectionOrderingApplicator orders each unordered collection navigation in a
Select lambda by its entity's default, after any Where and before the
operators that project the entity away or choose which rows come back. A
navigation bound into the result as it is is ordered too. Collections only
aggregated, or composed with an operator EF Core drops ordering beneath such
as Distinct, are left alone. Explicit ordering in a projection is kept and
checked by redundant ordering detection.

Version bumped to 1.4.0.
@SimonCropp SimonCropp added this to the 1.4.0 milestone Nov 4, 2026
@SimonCropp
SimonCropp merged commit a7da25f into main Nov 4, 2026
6 checks passed
@SimonCropp
SimonCropp deleted the order-projected-collections branch November 4, 2026 00:48
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant