Fix projection of required complex type via left join#37928
Open
roji wants to merge 1 commit intodotnet:release/10.0from
Open
Fix projection of required complex type via left join#37928roji wants to merge 1 commit intodotnet:release/10.0from
roji wants to merge 1 commit intodotnet:release/10.0from
Conversation
There was a problem hiding this comment.
Pull request overview
Fixes a regression in EF Core 10 complex-type materialization when entities are produced via LEFT JOINs (e.g., Include), ensuring required complex properties are still materialized even when all their scalar columns are NULL.
Changes:
- Adjust complex-property nullability propagation during materialization so required complex properties don’t inherit the parent structural type’s “nullable due to LEFT JOIN” state.
- Add an AppContext switch (
Microsoft.EntityFrameworkCore.Issue37304) to allow reverting to the previous behavior if needed. - Add a regression test covering required complex type materialization via a LEFT JOIN include.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated no comments.
| File | Description |
|---|---|
| test/EFCore.Specification.Tests/Query/AdHocComplexTypeQueryTestBase.cs | Adds a regression test asserting a required complex type is materialized (non-null) via LEFT JOIN include even when all its properties are null. |
| src/EFCore/Query/Internal/StructuralTypeMaterializerSource.cs | Stops cascading parent nullability into nested complex-property materialization (with an opt-out AppContext switch). |
You can also share your feedback on Copilot code review. Take the survey.
Member
Author
|
/azp run |
|
Azure Pipelines successfully started running 1 pipeline(s). |
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 #37304
Description
PR #37196 fixed #37162 by adding an IsNullable flag to StructuralTypeMaterializerSourceParameters to correctly distinguish optional vs required complex types during materialization. However, in StructuralTypeMaterializerSource.AddInitializeExpression, the IsNullable for nested complex properties was computed as nullable || complexProperty.IsNullable, where nullable is the parent entity/type's nullability. When an entity is loaded via a LEFT JOIN (e.g. Include on a nullable navigation), its IsNullable is true, and this incorrectly cascades down to all its required complex types, causing HandleNullableComplexTypeMaterialization to check their scalar properties and return null when all are null.
The fix removes the nullable || from the expression, using only complexProperty.IsNullable to determine the complex property's nullability. The parent's nullability is already handled at the entity level (discriminator/key check); once the entity is confirmed to exist, its required complex types must always be materialized as non-null.
Customer impact
When loading a non-optional complex type (with all-nullable properties) on an entity accessed via a LEFT JOIN — such as through Include on a collection navigation or a nullable reference navigation — EF Core 10 incorrectly returns null for the complex type instead of a non-null instance with null properties. This is a regression from EF 8/9 and violates the user's non-nullable configuration.
How found
Multiple customers reported on 10.0 (#37304)
Regression
Yes — introduced by the fix for #37162 (PR #37196) in 10.0.1.
Testing
Added regression test Non_optional_complex_type_with_all_nullable_properties_via_left_join in AdHocComplexTypeQueryTestBase.cs.
Risk
Very low. Single-character change (nullable || complexProperty.IsNullable → complexProperty.IsNullable). The parent entity's nullability was never needed here — entity-level null checks already prevent complex type materialization from being reached when the entity doesn't exist. Quirk added just in case.