Preserve identifiers across to-one joins - #38771
Conversation
Keep the outer collection identifiers when a to-one join has a keyless inner source, and cover single/split sync/async materialization.\n\nFixes dotnet#38660 Signed-off-by: XX-Q <52606527+XX-Q@users.noreply.github.com>
|
@XX-Q please read the following Contributor License Agreement(CLA). If you agree with the CLA, please reply with the following information.
Contributor License AgreementContribution License AgreementThis Contribution License Agreement ( “Agreement” ) is agreed to by the party signing below ( “You” ), 1. Definitions. “Code” means the computer software code, whether in human-readable or machine-executable form, “Project” means any of the projects owned or managed by .NET Foundation and offered under a license “Submit” is the act of uploading, submitting, transmitting, or distributing code or other content to any “Submission” means the Code and any other copyrightable material Submitted by You, including any 2. Your Submission. You must agree to the terms of this Agreement before making a Submission to any 3. Originality of Work. You represent that each of Your Submissions is entirely Your 4. Your Employer. References to “employer” in this Agreement include Your employer or anyone else 5. Licenses. a. Copyright License. You grant .NET Foundation, and those who receive the Submission directly b. Patent License. You grant .NET Foundation, and those who receive the Submission directly or c. Other Rights Reserved. Each party reserves all rights not expressly granted in this Agreement. 6. Representations and Warranties. You represent that You are legally entitled to grant the above 7. Notice to .NET Foundation. You agree to notify .NET Foundation in writing of any facts or 8. Information about Submissions. You agree that contributions to Projects and information about 9. Governing Law/Jurisdiction. This Agreement is governed by the laws of the State of Washington, and 10. Entire Agreement/Assignment. This Agreement is the entire agreement between the parties, and .NET Foundation dedicates this Contribution License Agreement to the public domain according to the Creative Commons CC0 1. |
There was a problem hiding this comment.
Pull request overview
Fixes a relational query materialization bug where a cardinality-preserving (to-one) join with a keyless inner source could incorrectly clear outer collection identifiers, leading to silently missing collection elements in single-query mode and an ArgumentOutOfRangeException in split-query mode. The change updates identifier handling during join composition and adds a regression test covering sync/async and single/split query execution.
Changes:
- Adjust join identifier propagation logic in
SelectExpression.AddJointo preserve outer identifiers for to-one joins even when the inner side has no identifiers. - Add a new regression test reproducing the keyless set-operation inner source scenario and validating correct collection materialization across sync/async and single/split query modes.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated no comments.
| File | Description |
|---|---|
| test/EFCore.Relational.Specification.Tests/Query/AdHocQuerySplittingQueryTestBase.cs | Adds regression coverage for the keyless-inner to-one join scenario across sync/async and single/split query materialization. |
| src/EFCore.Relational/Query/SqlExpressions/SelectExpression.cs | Updates join identifier clearing logic to avoid dropping outer collection identifiers for to-one joins with keyless inner sources. |
💡 Add a code-review agent skill for context-aware, tailored reviews. Learn more in the docs.
| protected override void OnModelCreating(ModelBuilder modelBuilder) | ||
| { | ||
| modelBuilder.Entity<OrderLine>().HasKey(l => new { l.OrderId, l.LineNo }); | ||
| modelBuilder.Entity<Order>().HasMany(o => o.Lines).WithOne().HasForeignKey(l => l.OrderId); |
There was a problem hiding this comment.
You need to disable value generation for Order.Id
There was a problem hiding this comment.
Fixed in 0f8a433: Order.Id now uses ValueGeneratedNever(), and TaxA/TaxB no longer seed explicit identity values. The focused 4-case regression test and the full 21-test SQLite class both pass locally.
Signed-off-by: XX-Q <52606527+XX-Q@users.noreply.github.com>
Summary
When the inner side of a cardinality-preserving join has no identifiers, clearing the outer identifiers loses the collection element identity. Single-query materialization then collapses rows, while split-query materialization indexes an empty comparer list. A to-one join cannot increase cardinality, so the existing outer identifiers remain sufficient.
Fixes #38660
Validation
ArgumentOutOfRangeExceptionbehaviorsAdHocQuerySplittingQuerySqliteTest: 21 passedEFCore.Relational.Tests: 1,480 passed, 1 skippedEFCore.Sqlite.FunctionalTests: 37,917 passed, 283 skipped./build.sh --build: passed with 0 warnings and 0 errorsSQL Server functional tests were not run locally because no SQL Server instance is available; the shared relational test will exercise the SQL Server provider in CI.
Checklist
Fixes #38660AI assistance
OpenAI Codex assisted with implementation and test execution. The final diff was reviewed against the maintainer-provided root cause and validated with the commands above.