Skip to content

[release/11.0] Translate set operations over null-checked subqueries - #38847

Open
AndriySvyryd wants to merge 4 commits into
dotnet:release/11.0from
AndriySvyryd:andriysvyryd-fix-set-operation-null-subquery
Open

[release/11.0] Translate set operations over null-checked subqueries#38847
AndriySvyryd wants to merge 4 commits into
dotnet:release/11.0from
AndriySvyryd:andriysvyryd-fix-set-operation-null-subquery

Conversation

@AndriySvyryd

@AndriySvyryd AndriySvyryd commented Aug 22, 2026

Copy link
Copy Markdown
Member
  • I've read the guidelines for contributing and seen the walkthrough
  • I've posted a comment on an issue with a detailed description of how I am planning to contribute and got approval from a member of the team
  • The code builds and tests pass locally (also verified by our automated build checks)
  • Commit messages follow this format:
        Summary of the changes
        - Detail 1
        - Detail 2

        Fixes #bugnumber
  • Tests for the changes have been added (for bug fixes / features)
  • Code follows the same patterns and style as existing code in this repo

Fixes #38838

This is part of the broader work tracked by #30915.

Description
PR #38479 introduced nullability markers for non-entity projections. When a to-one non-entity subquery was referenced multiple times and compared to null, its marker check remained in the client projection. Relational set operations then rejected the query even though all final values were SQL-translatable.

This change retries server projection binding only when a set-operation operand still has client projections after single-result subqueries have been lowered. A retry-only preprocessing step simplifies synthetic marker-backed object null comparisons into marker null checks so the operand can be rebound to SQL. If complete translation still fails, the original client projection remains and existing rejection behavior is preserved.

Customer impact
Queries shaped like the following throw instead of translating on 11.0 preview 7:

var withOrders = from c in context.Customers
                 let latest = c.Orders.Select(o => new { o.OrderID }).FirstOrDefault()
                 select new { c.CustomerID, OrderId = latest != null ? latest.OrderID : 0 };
await withOrders.Concat(otherQuery).ToListAsync();

A workaround exists by projecting a required property as nullable and checking that property instead of the object, but it is non-obvious and requires restructuring the projection.

How found
User reported on 11.0 preview 7. The issue has one reporter, two comment authors, and one reaction.

Regression
This regressed between 11.0 preview 6 and preview 7 and was introduced by PR #38479.

Testing
The regression and related client-projection scenarios are covered across SQL Server and SQLite. The complete solution test suite was run locally; assemblies affected by transient parallel SQL database creation races were rerun sequentially and passed.

Risk
Low. The retry is scoped to relational set-operation operands that still contain client projections, leaving ordinary client projection behavior unchanged. Quirk added: Microsoft.EntityFrameworkCore.Issue38838.

AndriySvyryd and others added 2 commits August 21, 2026 20:39
- Retry server projection binding after lowering single-result subqueries
- Simplify null checks over non-entity nullability markers
- Add SQL Server and SQLite regression coverage

Fixes dotnet#38838

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
- Allow opting out of projection retry and marker-null simplification

Fixes dotnet#38838

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot AI lite review requested due to automatic review settings August 22, 2026 03:52
@AndriySvyryd
AndriySvyryd requested a review from a team as a code owner August 22, 2026 03:52

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 an EF Core 11.0 regression where relational set operations (e.g. Concat/Union) fail to translate when a projection contains a null-checked, to-one, non-entity subquery referenced multiple times. The change is implemented in relational projection binding so that marker-backed object null comparisons become marker null tests, and server-side projection binding is retried after lowering single-result subqueries—allowing set operations to compose when the final projection is fully SQL-translatable.

Changes:

  • Update relational projection binding to (1) rewrite == null/!= null over marker-gated non-entity projections to marker null tests, and (2) retry member-based projection binding after single-result subquery lowering (with a fallback to the original client-projection path).
  • Add/adjust relational specification tests to cover the regression shape (Issue #38838) and validate set-operation translation over these projections.
  • Update SQL Server/SQLite functional test baselines to assert the newly translated SQL for the affected scenarios.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated no comments.

Show a summary per file
File Description
src/EFCore.Relational/Query/Internal/RelationalProjectionBindingExpressionVisitor.cs Implements the translation fix: simplifies marker-gated object null comparisons and retries member-based binding after lowering single-result subqueries (with opt-out switch).
test/EFCore.Relational.Specification.Tests/Query/NorthwindSetOperationsQueryRelationalTestBase.cs Adds regression coverage for set operations over null-checked to-one non-entity subqueries (Concat + Union).
test/EFCore.Relational.Specification.Tests/Query/AdHocMiscellaneousQueryRelationalTestBase.cs Updates the union-of-two-leftjoin-nonentity scenario to assert results now that translation is expected to succeed.
test/EFCore.SqlServer.FunctionalTests/Query/NorthwindSetOperationsQuerySqlServerTest.cs Adds SQL baseline assertions for the new Northwind regression test on SQL Server.
test/EFCore.Sqlite.FunctionalTests/Query/NorthwindSetOperationsQuerySqliteTest.cs Adds SQL baseline assertions for the new Northwind regression test on SQLite.
test/EFCore.SqlServer.FunctionalTests/Query/AdHocMiscellaneousQuerySqlServerTest.cs Adds SQL baseline assertions for the ad-hoc union translation scenario on SQL Server.

💡 Add a code-review agent skill for context-aware, tailored reviews. Learn more in the docs.

Retry lowered client projections only for set-operation operands and simplify the synthetic nullability-marker check before rebinding. This preserves ordinary client evaluation while allowing the regression query to translate.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 618bf8ca-f46b-4556-9c19-2cdb4e954c9a
Copilot AI review requested due to automatic review settings August 22, 2026 16:40

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 8 out of 8 changed files in this pull request and generated 1 comment.

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

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 8 out of 8 changed files in this pull request and generated 1 comment.

Comment on lines +53 to +57
private sealed class MarkerNullCheckSimplifyingExpressionVisitor : ExpressionVisitor
{
protected override Expression VisitBinary(BinaryExpression node)
{
if (node is { NodeType: ExpressionType.Equal or ExpressionType.NotEqual, Method: null }
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.

2 participants