Skip to content

Commit 47f8393

Browse files
authored
Fix to #30996 - Incorrect translation of comparison of current value with owned type default value (#31714)
In optional dependent table sharing scenario, when comparing the dependent to null we first look for any required properties (at least one of them needs to be null), and if we don't have any required properties we look at optional ones (even though this is somewhat ambiguous). Error was that we did similar check as with required properties (i.e. at least one of them must be null), but what we should be doing is checking that all of them are null. Fixes #30996
1 parent 301b01b commit 47f8393

File tree

3 files changed

+104
-6
lines changed

3 files changed

+104
-6
lines changed

src/EFCore.Relational/Query/RelationalSqlTranslatingExpressionVisitor.cs

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1791,19 +1791,23 @@ bool TryRewriteEntityEquality([NotNullWhen(true)] out Expression? result)
17911791
if (allNonPrincipalSharedNonPkProperties.Count != 0
17921792
&& allNonPrincipalSharedNonPkProperties.All(p => p.IsNullable))
17931793
{
1794-
var atLeastOneNonNullValueInNullablePropertyCondition = allNonPrincipalSharedNonPkProperties
1794+
// if we don't have any required properties to properly check the nullability,
1795+
// we rely on optional ones (somewhat unreliably)
1796+
// - if entity is to be null, all the properties must be null
1797+
// - if the entity is to be not null, at least one property must be not null
1798+
var optionalPropertiesCondition = allNonPrincipalSharedNonPkProperties
17951799
.Select(
17961800
p => Infrastructure.ExpressionExtensions.CreateEqualsExpression(
17971801
CreatePropertyAccessExpression(nonNullEntityReference, p),
17981802
Expression.Constant(null, p.ClrType.MakeNullable()),
17991803
nodeType != ExpressionType.Equal))
1800-
.Aggregate((l, r) => nodeType == ExpressionType.Equal ? Expression.OrElse(l, r) : Expression.AndAlso(l, r));
1804+
.Aggregate((l, r) => nodeType == ExpressionType.Equal ? Expression.AndAlso(l, r) : Expression.OrElse(l, r));
18011805

18021806
condition = condition == null
1803-
? atLeastOneNonNullValueInNullablePropertyCondition
1807+
? optionalPropertiesCondition
18041808
: nodeType == ExpressionType.Equal
1805-
? Expression.OrElse(condition, atLeastOneNonNullValueInNullablePropertyCondition)
1806-
: Expression.AndAlso(condition, atLeastOneNonNullValueInNullablePropertyCondition);
1809+
? Expression.OrElse(condition, optionalPropertiesCondition)
1810+
: Expression.AndAlso(condition, optionalPropertiesCondition);
18071811
}
18081812

18091813
if (condition != null)

test/EFCore.Relational.Specification.Tests/Query/OwnedEntityQueryRelationalTestBase.cs

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -284,6 +284,64 @@ public virtual async Task Owned_entity_with_all_null_properties_entity_equality_
284284
});
285285
}
286286

287+
[ConditionalTheory]
288+
[MemberData(nameof(IsAsyncData))]
289+
public virtual async Task Owned_entity_with_all_null_properties_in_compared_to_null_in_conditional_projection(bool async)
290+
{
291+
var contextFactory = await InitializeAsync<MyContext28247>(seed: c => c.Seed());
292+
293+
using var context = contextFactory.CreateContext();
294+
var query = context.RotRutCases
295+
.AsNoTracking()
296+
.OrderBy(e => e.Id)
297+
.Select(e => e.Rot == null ? null : new RotDto { MyApartmentNo = e.Rot.ApartmentNo, MyServiceType = e.Rot.ServiceType });
298+
299+
var result = async
300+
? await query.ToListAsync()
301+
: query.ToList();
302+
303+
Assert.Collection(
304+
result,
305+
t =>
306+
{
307+
Assert.Equal("1", t.MyApartmentNo);
308+
Assert.Equal(1, t.MyServiceType);
309+
},
310+
t =>
311+
{
312+
Assert.Null(t);
313+
});
314+
}
315+
316+
[ConditionalTheory]
317+
[MemberData(nameof(IsAsyncData))]
318+
public virtual async Task Owned_entity_with_all_null_properties_in_compared_to_non_null_in_conditional_projection(bool async)
319+
{
320+
var contextFactory = await InitializeAsync<MyContext28247>(seed: c => c.Seed());
321+
322+
using var context = contextFactory.CreateContext();
323+
var query = context.RotRutCases
324+
.AsNoTracking()
325+
.OrderBy(e => e.Id)
326+
.Select(e => e.Rot != null ? new RotDto { MyApartmentNo = e.Rot.ApartmentNo, MyServiceType = e.Rot.ServiceType } : null);
327+
328+
var result = async
329+
? await query.ToListAsync()
330+
: query.ToList();
331+
332+
Assert.Collection(
333+
result,
334+
t =>
335+
{
336+
Assert.Equal("1", t.MyApartmentNo);
337+
Assert.Equal(1, t.MyServiceType);
338+
},
339+
t =>
340+
{
341+
Assert.Null(t);
342+
});
343+
}
344+
287345
[ConditionalTheory]
288346
[MemberData(nameof(IsAsyncData))]
289347
public virtual async Task Owned_entity_with_all_null_properties_property_access_when_not_containing_another_owned_entity(bool async)
@@ -364,6 +422,12 @@ public class Rot
364422
public string ApartmentNo { get; set; }
365423
}
366424

425+
public class RotDto
426+
{
427+
public int? MyServiceType { get; set; }
428+
public string MyApartmentNo { get; set; }
429+
}
430+
367431
public class Rut
368432
{
369433
public int? Value { get; set; }

test/EFCore.SqlServer.FunctionalTests/Query/OwnedEntityQuerySqlServerTest.cs

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -166,7 +166,37 @@ public override async Task Owned_entity_with_all_null_properties_entity_equality
166166
"""
167167
SELECT [r].[Id], [r].[Rot_ApartmentNo], [r].[Rot_ServiceType]
168168
FROM [RotRutCases] AS [r]
169-
WHERE [r].[Rot_ApartmentNo] IS NOT NULL AND [r].[Rot_ServiceType] IS NOT NULL
169+
WHERE [r].[Rot_ApartmentNo] IS NOT NULL OR [r].[Rot_ServiceType] IS NOT NULL
170+
""");
171+
}
172+
173+
public override async Task Owned_entity_with_all_null_properties_in_compared_to_null_in_conditional_projection(bool async)
174+
{
175+
await base.Owned_entity_with_all_null_properties_in_compared_to_null_in_conditional_projection(async);
176+
177+
AssertSql(
178+
"""
179+
SELECT CASE
180+
WHEN [r].[Rot_ApartmentNo] IS NULL AND [r].[Rot_ServiceType] IS NULL THEN CAST(1 AS bit)
181+
ELSE CAST(0 AS bit)
182+
END, [r].[Rot_ApartmentNo], [r].[Rot_ServiceType]
183+
FROM [RotRutCases] AS [r]
184+
ORDER BY [r].[Id]
185+
""");
186+
}
187+
188+
public override async Task Owned_entity_with_all_null_properties_in_compared_to_non_null_in_conditional_projection(bool async)
189+
{
190+
await base.Owned_entity_with_all_null_properties_in_compared_to_non_null_in_conditional_projection(async);
191+
192+
AssertSql(
193+
"""
194+
SELECT CASE
195+
WHEN [r].[Rot_ApartmentNo] IS NOT NULL OR [r].[Rot_ServiceType] IS NOT NULL THEN CAST(1 AS bit)
196+
ELSE CAST(0 AS bit)
197+
END, [r].[Rot_ApartmentNo], [r].[Rot_ServiceType]
198+
FROM [RotRutCases] AS [r]
199+
ORDER BY [r].[Id]
170200
""");
171201
}
172202

0 commit comments

Comments
 (0)