Skip to content

Commit fba8789

Browse files
rojiranma42
andauthored
Avoid using ^ and ~ when invalid because of value converters (#35124) (#35241)
The transformation of equality/in-equality in a (negated) XOR is only possible when the expressions are BIT or integer types on the SQL side (i.e. taking value conversion into account). Similarly, the Boolean negation `NOT` can be implemented as `~` only if the underlying expression is a BIT. Fixes #35093. (cherry picked from commit e6abfdd) Co-authored-by: Andrea Canciani <[email protected]>
1 parent 1c0ef32 commit fba8789

File tree

7 files changed

+86
-3
lines changed

7 files changed

+86
-3
lines changed

src/EFCore.SqlServer/Query/Internal/SearchConditionConvertingExpressionVisitor.cs

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,9 @@ public class SearchConditionConvertingExpressionVisitor : SqlExpressionVisitor
1616
private bool _isSearchCondition;
1717
private readonly ISqlExpressionFactory _sqlExpressionFactory;
1818

19+
private static readonly bool UseOldBehavior35093 =
20+
AppContext.TryGetSwitch("Microsoft.EntityFrameworkCore.Issue35093", out var enabled35093) && enabled35093;
21+
1922
/// <summary>
2023
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
2124
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
@@ -344,9 +347,12 @@ protected override Expression VisitSqlBinary(SqlBinaryExpression sqlBinaryExpres
344347

345348
_isSearchCondition = parentIsSearchCondition;
346349

350+
var leftType = UseOldBehavior35093 ? newLeft.Type : newLeft.TypeMapping?.Converter?.ProviderClrType ?? newLeft.Type;
351+
var rightType = UseOldBehavior35093 ? newRight.Type : newRight.TypeMapping?.Converter?.ProviderClrType ?? newRight.Type;
352+
347353
if (!parentIsSearchCondition
348-
&& (newLeft.Type == typeof(bool) || newLeft.Type.IsEnum || newLeft.Type.IsInteger())
349-
&& (newRight.Type == typeof(bool) || newRight.Type.IsEnum || newRight.Type.IsInteger())
354+
&& (leftType == typeof(bool) || leftType.IsEnum || leftType.IsInteger())
355+
&& (rightType == typeof(bool) || rightType.IsEnum || rightType.IsInteger())
350356
&& sqlBinaryExpression.OperatorType is ExpressionType.NotEqual or ExpressionType.Equal)
351357
{
352358
// "lhs != rhs" is the same as "CAST(lhs ^ rhs AS BIT)", except that
@@ -410,7 +416,10 @@ protected override Expression VisitSqlUnary(SqlUnaryExpression sqlUnaryExpressio
410416
switch (sqlUnaryExpression.OperatorType)
411417
{
412418
case ExpressionType.Not
413-
when sqlUnaryExpression.Type == typeof(bool):
419+
when (UseOldBehavior35093
420+
? sqlUnaryExpression.Type
421+
: (sqlUnaryExpression.TypeMapping?.Converter?.ProviderClrType ?? sqlUnaryExpression.Type))
422+
== typeof(bool):
414423
{
415424
// when possible, avoid converting to/from predicate form
416425
if (!_isSearchCondition && sqlUnaryExpression.Operand is not (ExistsExpression or InExpression or LikeExpression))

test/EFCore.Specification.Tests/Query/GearsOfWarQueryTestBase.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8047,6 +8047,13 @@ public virtual Task Comparison_with_value_converted_subclass(bool async)
80478047
async,
80488048
ss => ss.Set<Faction>().Where(f => f.ServerAddress == IPAddress.Loopback));
80498049

8050+
[ConditionalTheory]
8051+
[MemberData(nameof(IsAsyncData))]
8052+
public virtual Task Project_equality_with_value_converted_property(bool async)
8053+
=> AssertQuery(
8054+
async,
8055+
ss => ss.Set<Mission>().Select(m => m.Difficulty == MissionDifficulty.Unknown));
8056+
80508057
private static readonly IEnumerable<AmmunitionType?> _weaponTypes = new AmmunitionType?[] { AmmunitionType.Cartridge };
80518058

80528059
[ConditionalTheory]

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

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9380,6 +9380,20 @@ FROM [Factions] AS [f]
93809380
""");
93819381
}
93829382

9383+
public override async Task Project_equality_with_value_converted_property(bool async)
9384+
{
9385+
await base.Project_equality_with_value_converted_property(async);
9386+
9387+
AssertSql(
9388+
"""
9389+
SELECT CASE
9390+
WHEN [m].[Difficulty] = N'Unknown' THEN CAST(1 AS bit)
9391+
ELSE CAST(0 AS bit)
9392+
END
9393+
FROM [Missions] AS [m]
9394+
""");
9395+
}
9396+
93839397
public override async Task Contains_on_readonly_enumerable(bool async)
93849398
{
93859399
await base.Contains_on_readonly_enumerable(async);

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

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12341,6 +12341,20 @@ FROM [LocustHordes] AS [l]
1234112341
""");
1234212342
}
1234312343

12344+
public override async Task Project_equality_with_value_converted_property(bool async)
12345+
{
12346+
await base.Project_equality_with_value_converted_property(async);
12347+
12348+
AssertSql(
12349+
"""
12350+
SELECT CASE
12351+
WHEN [m].[Difficulty] = N'Unknown' THEN CAST(1 AS bit)
12352+
ELSE CAST(0 AS bit)
12353+
END
12354+
FROM [Missions] AS [m]
12355+
""");
12356+
}
12357+
1234412358
public override async Task FirstOrDefault_on_empty_collection_of_DateTime_in_subquery(bool async)
1234512359
{
1234612360
await base.FirstOrDefault_on_empty_collection_of_DateTime_in_subquery(async);

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

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10507,6 +10507,20 @@ FROM [Factions] AS [f]
1050710507
""");
1050810508
}
1050910509

10510+
public override async Task Project_equality_with_value_converted_property(bool async)
10511+
{
10512+
await base.Project_equality_with_value_converted_property(async);
10513+
10514+
AssertSql(
10515+
"""
10516+
SELECT CASE
10517+
WHEN [m].[Difficulty] = N'Unknown' THEN CAST(1 AS bit)
10518+
ELSE CAST(0 AS bit)
10519+
END
10520+
FROM [Missions] AS [m]
10521+
""");
10522+
}
10523+
1051010524
public override async Task FirstOrDefault_on_empty_collection_of_DateTime_in_subquery(bool async)
1051110525
{
1051210526
await base.FirstOrDefault_on_empty_collection_of_DateTime_in_subquery(async);

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

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6999,6 +6999,20 @@ public override async Task Comparison_with_value_converted_subclass(bool async)
69996999
""");
70007000
}
70017001

7002+
public override async Task Project_equality_with_value_converted_property(bool async)
7003+
{
7004+
await base.Project_equality_with_value_converted_property(async);
7005+
7006+
AssertSql(
7007+
"""
7008+
SELECT CASE
7009+
WHEN [m].[Difficulty] = N'Unknown' THEN CAST(1 AS bit)
7010+
ELSE CAST(0 AS bit)
7011+
END
7012+
FROM [Missions] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [m]
7013+
""");
7014+
}
7015+
70027016
public override async Task Navigation_access_on_derived_materialized_entity_using_cast(bool async)
70037017
{
70047018
await base.Navigation_access_on_derived_materialized_entity_using_cast(async);

test/EFCore.Sqlite.FunctionalTests/Query/GearsOfWarQuerySqliteTest.cs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3076,6 +3076,17 @@ public override async Task Comparison_with_value_converted_subclass(bool async)
30763076
""");
30773077
}
30783078

3079+
public override async Task Project_equality_with_value_converted_property(bool async)
3080+
{
3081+
await base.Project_equality_with_value_converted_property(async);
3082+
3083+
AssertSql(
3084+
"""
3085+
SELECT "m"."Difficulty" = 'Unknown'
3086+
FROM "Missions" AS "m"
3087+
""");
3088+
}
3089+
30793090
public override async Task GetValueOrDefault_in_filter_non_nullable_column(bool async)
30803091
{
30813092
await base.GetValueOrDefault_in_filter_non_nullable_column(async);

0 commit comments

Comments
 (0)