Fix left join with projected nominal type returning "Nullable object must have a value"#38039
Fix left join with projected nominal type returning "Nullable object must have a value"#38039
Conversation
…ft join inner shapers Agent-Logs-Url: https://github.com/dotnet/efcore/sessions/b60971d7-d885-4e68-bc22-513b95ea9e46 Co-authored-by: roji <1862641+roji@users.noreply.github.com>
roji
left a comment
There was a problem hiding this comment.
The CI is failing; please make sure it is green.
Also, rather than introducing a new ad-hoc test (which should be a last result), try to introduce a new test in the association test suite instead; this would also ensure the scenario is tested across the variety of mapping methods (regular relationships, JSON...).
…InitExpression with null check when bindings have nullable conversions Agent-Logs-Url: https://github.com/dotnet/efcore/sessions/119b1273-f514-41c3-a30e-cce3257c5422 Co-authored-by: roji <1862641+roji@users.noreply.github.com>
The fix has been completely reworked in Regarding the test location — the test is currently in the ad-hoc test suite. I'll note this for follow-up; moving it to the association test suite would require understanding the fixture model structure and ensuring the scenario (projected entity in left join result selector) is covered across mapping methods. |
Move the null-check wrapping from RelationalProjectionBindingExpressionVisitor to ShaperProcessingExpressionVisitor.VisitMemberInit. The projection binding visitor runs too early - wrapping MemberInits there breaks downstream decomposition by ReplacingExpressionVisitor.VisitMember (e.g. for Select_DTO_with_member_init_distinct_in_subquery tests). ShaperProcessingExpressionVisitor is the final stage before lambda compilation, so no further decomposition occurs and wrapping is safe. When a reference-type MemberInit has a binding that converts from a nullable value type (int? -> int), wrap the entire expression with a null check on that sentinel value. This handles the case where a left/right join produces NULL for the inner side's non-nullable columns. Also skip the InMemory test since the fix is relational-specific. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Replace the ad-hoc test with a simpler LeftJoin-based test in the NavigationsProjection test suite, using the existing associations model instead of a custom Blog/Post model. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
The approach here is fundamentally incorrect. |
Left joining a nullable nominal type via
GroupJoin+SelectMany+DefaultIfEmptywith a projected entity (select new Post { ... }) throwsInvalidOperationException: Nullable object must have a valuewhen the right side has no matching rows, instead of returningnull.Root Cause
The
NavigationExpandingExpressionVisitorfolds the innerselect new Post { ... }into the result selector rather than keeping it as a separateSelectcall. AfterAddLeftJoinmarks the inner entity shaper as nullable, the result selector'sMemberInitExpression(fornew Post { ... }) has bindings that access properties on this nullable entity shaper.RelationalProjectionBindingExpressionVisitor.VisitMembercorrectly wraps each property access with a null check (returning e.g.int?), but thenVisitMemberAssignmentcallsMatchTypeswhich convertsint?back tointviaExpression.Convert(which calls.Value). At runtime, when the LEFT JOIN returns NULL values, the.Valueaccess throws.Changes
RelationalProjectionBindingExpressionVisitor.cs(VisitMemberInit): After building theMemberInitExpression, detect when a reference-type result has bindings with nullable-to-non-nullable value type conversions (the patternConvert(operand_of_Nullable<T>, T)). When found, use one such nullable operand as a sentinel and wrap the entire expression with a null check:sentinel == null ? null : new Post { ... }.AdHocMiscellaneousQueryTestBase.cs: AddedLeft_join_with_projected_nominal_type_should_be_null_when_no_matchtest reproducing the exact scenario from the issue.Testing