Repository navigation
Keep conversions around a grouping key on the server - #39368
Merged
AndriySvyryd merged 1 commit intoOct 23, 2026
Merged
Conversation
AndriySvyryd
approved these changes
Oct 22, 2026
AndriySvyryd
left a comment
Member
There was a problem hiding this comment.
Thanks for your contribution!
Member
|
@benedict-odonovan Please rebase on latest main |
- A projection lifted out of a grouping ran a conversion around g.Key in its client selector, replacing the database's CAST semantics with .NET's: (decimal)g.Key over a double key no longer rounded to the mapped scale, and (int)g.Key over a long key wrapped around instead of failing on overflow. - Only conversions which can't change the value - to or from the nullable form, boxing, and reference conversions - are now applied on the client. Any other conversion makes the projection keep its non-lifted translation, which casts on the server. - Add tests for a key conversion which changes the value and one which doesn't. Fixes dotnet#39367
benedict-odonovan
force-pushed
the
fix/groupby-lift-key-conversion
branch
from
October 23, 2026 09:05
21d6b49 to
26fe02c
Compare
AndriySvyryd
approved these changes
Oct 23, 2026
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 #39367
Summary
Since #38963, a projection that enumerates a grouping's elements is lifted. It's translated as a single scan, and the rest of the projection is applied on the client to each materialized grouping. That client selector accepted any conversion around
g.Key, so(decimal)g.Keyor(int)g.Keyran in .NET instead of as a SQLCAST, and the results changed:release/11.0main(decimal)g.Keyover adoublekey 1.237 (SQL Server)1.24(CAST(... AS decimal(18,2)))1.2370000000000000994759830064(int)g.Keyover alongkey 3,000,000,000SqlException: arithmetic overflow-1294967296(int?)g.Keyover amoneykey 2.5 (SQL Server)3(CASTrounds)2(.NET truncates)The same key next to an aggregate,
Select(g => new { Key = (decimal)g.Key, Count = g.Count() }), is still cast on the server, so two projections of the same key disagreed.Change
GroupingElementProjectionAnalyzer.Visitnow lets a conversion through to the client selector only when it can't change the value:Nullable<T>;Any other conversion, including every conversion that has a
Method(op_Explicit,op_Implicit), marks the projection as unsupported. It then keeps its non-lifted translation, with theCASTon the server as before.This gives up the single scan only for projections that convert the key to a different type. The alternative was to translate each conversion and lift only when it translates to nothing, as #39240 does for a conversion around an aggregate. That would keep a few more projections lifted, but it needs the SQL translator inside the analyzer, which doesn't have it today.
Note on null keys. A projection that falls back to the non-lifted translation is subject to #32026: with a nullable key, the null group loses its elements. That's fixed separately in #39365. The two PRs are independent and touch different files.
Tests
GroupBy_selecting_grouping_element_list_with_converted_key:(int)g.Keyover ashortkey is no longer lifted, so the SQL hasCAST(... AS int)andGROUP BY. The conversion was chosen so that SQL and LINQ-to-Objects agree on the values, since the spec tests compare against LINQ-to-Objects; the baseline is what shows the server-side cast. (UnitPricewould show the rounding difference, but then the test's expected LINQ-to-Objects results would be the wrong ones.)GroupBy_selecting_grouping_element_list_with_nullable_converted_key:(uint?)g.Keystays lifted (DENSE_RANKsingle scan).Ran locally (LocalDB). All pass, with no other baseline changes:
*GroupBy*,*ComplexNavigations*,*GearsOfWar*,*Northwind*,*PrecompiledQuery*