Fix unnecessary joins in consecutive Select projections with conditionals#37601
Merged
Conversation
Co-authored-by: roji <1862641+roji@users.noreply.github.com>
Co-authored-by: roji <1862641+roji@users.noreply.github.com>
Co-authored-by: roji <1862641+roji@users.noreply.github.com>
Co-authored-by: roji <1862641+roji@users.noreply.github.com>
When two consecutive Select() operations are applied with conditional expressions,
the second Select was including unnecessary joins and columns from the first Select.
Root cause: In IncludeExpandingExpressionVisitor.VisitMember, when accessing a
specific member of a NavigationTreeExpression with a NewExpression (anonymous type),
the code was reconstructing the entire anonymous type instead of just extracting
the accessed member.
Fix: Modified VisitMember to detect when a member is accessed on a NavigationTreeExpression
with a NewExpression value, and directly visit only the specific argument corresponding to
that member, avoiding reconstruction of the entire anonymous type.
This ensures that expressions like:
.Select(x => new { x.Job.Id, x.Job.Address.Street })
.Select(x => new { x.Job.Id })
Only include Job.Id in the final SQL, not Address properties.
Fixes #XXXXX
Co-authored-by: roji <1862641+roji@users.noreply.github.com>
Copilot
AI
changed the title
[WIP] Fix parameter check causing incorrect projections translation
Fix unnecessary joins in consecutive Select projections with conditionals
Jan 31, 2026
6 tasks
There was a problem hiding this comment.
Pull request overview
This PR addresses a query translation inefficiency where consecutive Select() projections combined with conditional null checks would cause EF Core to keep projecting earlier navigation members, resulting in unnecessary joins/columns in generated SQL. The change is implemented in the navigation-expansion pipeline and validated with a new relational test plus a SQLite SQL baseline to ensure the unreferenced navigation join is eliminated.
Changes:
- Adjust navigation expansion/member access handling to avoid expanding/reconstructing entire anonymous-type navigation shapes when only a specific member is accessed.
- Add a new relational specification test covering consecutive conditional projections.
- Add a SQLite functional test override asserting the absence of the unnecessary join in generated SQL.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 4 comments.
| File | Description |
|---|---|
| src/EFCore/Query/Internal/NavigationExpandingExpressionVisitor.ExpressionVisitors.cs | Adds/changes visitors to prune navigation-tree member expansions so later projections don’t carry unused navigation joins. |
| src/EFCore/Query/Internal/NavigationExpandingExpressionVisitor.cs | Runs the new pruning visitor during Select processing. |
| test/EFCore.Relational.Specification.Tests/Query/AdHocNavigationsQueryRelationalTestBase.cs | Adds a new relational spec test reproducing the consecutive conditional projection scenario. |
| test/EFCore.Sqlite.FunctionalTests/Query/AdHocNavigationsQuerySqliteTest.cs | Adds SQL assertion ensuring the generated query doesn’t include the unnecessary join. |
Co-authored-by: AndriySvyryd <6539701+AndriySvyryd@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
…tests Co-authored-by: AndriySvyryd <6539701+AndriySvyryd@users.noreply.github.com>
Co-authored-by: AndriySvyryd <6539701+AndriySvyryd@users.noreply.github.com>
Co-authored-by: AndriySvyryd <6539701+AndriySvyryd@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
AndriySvyryd
approved these changes
Jun 12, 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.
Consecutive
.Select()operations with conditional null checks were projecting all properties from earlier selects, generating unnecessary joins and columns in SQL.Generated SQL incorrectly included
Addresstable joins and columns despite the second select only referencingJob.Id.Changes
Core Fix (
NavigationExpandingExpressionVisitor.ExpressionVisitors.cs)NavigationTreeMemberPruningVisitorthat rewrites composed selectors before navigation expansion using three rules:(new { A = a, B = b }).A → a— prunes unused branches from anonymous-type projections(test ? null : ifFalse).M → ifFalse.M— folds member access through null-safe conditionals(test ? null : new { M = ... }) == null → test— simplifies null checks on conditional new-expressionsTests
Consecutive_selects_with_conditional_projection_should_not_include_unnecessary_joins— verifies generated SQL excludes unreferenced navigation joinsConsecutive_selects_with_conditional_projection_null_navigation_returns_null— verifies null navigation is correctly returned as nullConsecutive_selects_with_conditional_projection_nested_navigation_accessed_includes_join— verifies referenced navigations still produce their joinsConsecutive_selects_with_conditional_projection_should_not_include_unnecessary_joinswith materialization assertions forresult.Jobandresult.Job.IdOriginal prompt
💬 We'd love your input! Share your thoughts on Copilot coding agent in our 2 minute survey.