Repository navigation
Conversation
GroupBy(k).Select(g => g.OrderBy(o).First()) was translated as a GROUP BY joined back on the key to a ROW_NUMBER() subquery. That reads the source twice, and the join on the key loses the element of the group whose key is NULL. When a projection uses the grouping only through its key and one First, FirstOrDefault, Last or LastOrDefault over the elements, optionally ordered and projected (which covers MaxBy and MinBy), number the source rows within each group and keep the first, with no GROUP BY and no join. Other shapes keep today's translation. Fixes dotnet#36380 Fixes dotnet#29240 Signed-off-by: QHarshil <harshil_c@hotmail.com>
Contributor
Author
|
Reopened to rerun CI. The earlier jobs were cancelled before a runner picked them up. |
Contributor
There was a problem hiding this comment.
🔵 Needs a closer look
It changes mutable core query-translation behavior across many compositional and provider-specific paths, warranting final human review.
0 open findings
🧠 Review effort: Balanced
Give feedback about Copilot approvals in this survey to enter a drawing for a $150 gift card.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #36380
Fixes #29240
GroupBy(k).Select(g => g.OrderBy(o).First())translates to aGROUP BYjoined back on the key to aROW_NUMBER()subquery. That reads the source twice, and the=join loses the element of the NULL group:FirstOrDefault()returns null for it, andFirst()over a projected element throws "Sequence contains no elements" (#29240).This PR translates it to the
ROW_NUMBER()filter alone:It applies when the projection uses the grouping only through
g.Keyand oneFirst,FirstOrDefault,LastorLastOrDefaultover the elements, optionally ordered and projected, soMaxByandMinBytoo. Every group has a first row, so the groups are the same. Operators after the read compose over it, so aWhereon the element becomes a plainWHEREinstead of a correlatedTOP(1)subquery inHAVING.Today's translation stays for a filter,
SkiporElementAtin the group, aggregates, two element reads, scalar reads likeg.First().Name, GroupBy in a subquery or a correlatedSelectMany, and elements with included navigations when more operators follow. Those still lose the NULL group.Implementation
TryTranslateGroupingElementRead, next toTryTranslateGroupingElementProjectionfrom Stream GroupBy elements instead of joining the source back to itself #38963, matches the shape and translates the orderings before changing theSelectExpression. Any other shape falls through.SelectExpression.ApplyFirstRowPerGroupdrops the grouping and adds the partitionedROW_NUMBER()filter, using the fallback orderingsAddJoinuses.Performance
EF's SQL before and after, 5% NULL keys, median of 5. SQL Server 2025 ran in an x64 container on Apple Silicon.
First()First()First(), thenWhereFirst(), thenWhereNon-NULL groups returned the same rows in every run.
Tests
First,FirstOrDefault,LastOrDefault,MaxBy, a projected element, an element selector and the result-selector overload; aWhere/OrderBy/Takeafter the read; GroupBy nullable column followed by First() over group returns null record #29240's code; owned types; a precompiled query; and four shapes that keep today's translation, whose baselines show the old SQL.NullReferenceException.Group_by_json_scalar_Orderby_json_scalar_FirstOrDefaultwas skipped for JSON/Query: translate groupby followed by orderby json scalar + FirstOrDefault #29287 but passes on main, so I unskipped it.main. SQLite functional (38,512) and InMemory functional (28,396) pass, as do the unit test projects andEFCore.ApiBaseline.Tests.