Skip to content

Keep nullable outer-key null check in compound APPLY→JOIN predicates#38449

Open
Copilot wants to merge 6 commits into
mainfrom
copilot/fix-nullable-navigation-properties
Open

Keep nullable outer-key null check in compound APPLY→JOIN predicates#38449
Copilot wants to merge 6 commits into
mainfrom
copilot/fix-nullable-navigation-properties

Conversation

Copilot AI commented Jun 17, 2026

Copy link
Copy Markdown
Contributor

Fixes #35706

A collection navigation reached through an optional (nullable) key returned wrong results when combined with an additional filter: entities with a NULL key matched each other instead of yielding an empty collection.

context.People
    .Select(subject => new
    {
        subject.Name,
        Coworkers = subject.Employer!.Employees!
            .Where(employee => employee != subject) // extra filter makes the join predicate compound
            .Select(coworker => new { coworker.Name })
            .ToList(),
    });
// People with no Employer incorrectly listed all other employer-less people as coworkers.

Root cause: SelectExpression.RemoveRedundantNullChecks always stripped the outer-key IS NOT NULL check. For a single-comparison join that's safe (SqlNullabilityProcessor.ProcessJoinPredicate keeps SQL null semantics), but for a compound predicate the processor expands the equality with C# null semantics — adding OR (both sides NULL) — which then matches rows where both keys are NULL.

Changes

  • SelectExpression.RemoveRedundantNullChecks: retain the null check on a nullable outer key when the join predicate is compound (more than one key comparison). Single-comparison joins are unchanged. A small CountKeyComparisons helper distinguishes the two cases.
  • Baselines: updated affected composite-key navigation SQL in the GearsOfWar suites (SqlServer regular/TPC/TPT/Temporal and SQLite), which now emit outerKey IS NOT NULL AND ... instead of (outerKey = innerKey OR (both NULL)) AND ....
  • Regression test: Filtered_collection_through_optional_navigation_does_not_match_on_null_keys (specification base + SqlServer/SQLite SQL assertions).

Notes for review

  • The change widens to all compound collection-navigation joins over nullable keys, hence the broad-but-mechanical GearsOfWar baseline churn; results are unchanged for that data since the inner keys are PKs.
  • SqlServer functional tests were not run locally (no server); SqlServer baselines mirror the verified SQLite output exactly.

Copilot AI and others added 2 commits June 17, 2026 23:26
… APPLY to JOIN

Co-authored-by: AndriySvyryd <6539701+AndriySvyryd@users.noreply.github.com>
…Server baselines, add regression test

Co-authored-by: AndriySvyryd <6539701+AndriySvyryd@users.noreply.github.com>
…n fix

Co-authored-by: AndriySvyryd <6539701+AndriySvyryd@users.noreply.github.com>
Copilot AI changed the title [WIP] Fix nullable navigation properties leading to unexpected outer join Keep nullable outer-key null check in compound APPLY→JOIN predicates Jun 18, 2026
@AndriySvyryd AndriySvyryd requested a review from Copilot June 18, 2026 02:13

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Fixes incorrect results for collection navigations reached via an optional key when the correlated APPLY is converted to a JOIN with a compound predicate (e.g., additional filters), by ensuring outer nullable-key null checks aren’t stripped in cases where SqlNullabilityProcessor applies C# null-semantics expansion.

Changes:

  • Updated SelectExpression.RemoveRedundantNullChecks to retain nullable outer-key null checks when the extracted join predicate is compound.
  • Added a regression test covering filtered collections through an optional navigation, with provider-specific SQL assertions (SqlServer/Sqlite).
  • Updated affected SQL baselines across GearsOfWar suites (SqlServer + Sqlite) to reflect the new join predicate shape.

Reviewed changes

Copilot reviewed 9 out of 9 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
src/EFCore.Relational/Query/SqlExpressions/SelectExpression.cs Keeps nullable outer-key null checks for compound extracted join predicates to prevent incorrect null matching.
test/EFCore.Specification.Tests/Query/AdHocNavigationsQueryTestBase.cs Adds regression test for filtered collection through optional navigation not matching on null keys.
test/EFCore.SqlServer.FunctionalTests/Query/AdHocNavigationsQuerySqlServerTest.cs Adds SQL assertion baseline for the new regression test (SqlServer).
test/EFCore.Sqlite.FunctionalTests/Query/AdHocNavigationsQuerySqliteTest.cs Adds SQL assertion baseline for the new regression test (Sqlite).
test/EFCore.SqlServer.FunctionalTests/Query/GearsOfWarQuerySqlServerTest.cs Updates SQL baselines to include retained outer-key null checks / simplified predicates.
test/EFCore.SqlServer.FunctionalTests/Query/TemporalGearsOfWarQuerySqlServerTest.cs Updates temporal SQL baselines to include retained outer-key null checks / simplified predicates.
test/EFCore.SqlServer.FunctionalTests/Query/Inheritance/TPTGearsOfWarQuerySqlServerTest.cs Updates TPT SQL baselines for the adjusted join predicates.
test/EFCore.SqlServer.FunctionalTests/Query/Inheritance/TPCGearsOfWarQuerySqlServerTest.cs Updates TPC SQL baselines for the adjusted join predicates.
test/EFCore.Sqlite.FunctionalTests/Query/GearsOfWarQuerySqliteTest.cs Updates Sqlite SQL baselines for the adjusted join predicates.

Comment thread src/EFCore.Relational/Query/SqlExpressions/SelectExpression.cs Outdated
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
@AndriySvyryd AndriySvyryd marked this pull request as ready for review June 18, 2026 04:03
@AndriySvyryd AndriySvyryd requested a review from a team as a code owner June 18, 2026 04:03

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 9 out of 9 changed files in this pull request and generated 1 comment.

Comment thread src/EFCore.Relational/Query/SqlExpressions/SelectExpression.cs
Co-authored-by: AndriySvyryd <6539701+AndriySvyryd@users.noreply.github.com>
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.

Nullable navigation properties lead to unexpected outer join criteria

3 participants