Skip to content

Commit e6abfdd

Browse files
authored
Avoid using ^ and ~ when invalid because of value converters (dotnet#35124)
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 dotnet#35093.
1 parent d1e1dfa commit e6abfdd

File tree

7 files changed

+80
-3
lines changed

7 files changed

+80
-3
lines changed

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

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -206,9 +206,11 @@ protected virtual Expression VisitSqlBinary(SqlBinaryExpression binary, bool inS
206206

207207
if (binary.OperatorType is ExpressionType.NotEqual or ExpressionType.Equal)
208208
{
209+
var leftType = newLeft.TypeMapping?.Converter?.ProviderClrType ?? newLeft.Type;
210+
var rightType = newRight.TypeMapping?.Converter?.ProviderClrType ?? newRight.Type;
209211
if (!inSearchConditionContext
210-
&& (newLeft.Type == typeof(bool) || newLeft.Type.IsEnum || newLeft.Type.IsInteger())
211-
&& (newRight.Type == typeof(bool) || newRight.Type.IsEnum || newRight.Type.IsInteger()))
212+
&& (leftType == typeof(bool) || leftType.IsInteger())
213+
&& (rightType == typeof(bool) || rightType.IsInteger()))
212214
{
213215
// "lhs != rhs" is the same as "CAST(lhs ^ rhs AS BIT)", except that
214216
// the first is a boolean, the second is a BIT
@@ -262,7 +264,8 @@ protected virtual Expression VisitSqlUnary(SqlUnaryExpression sqlUnaryExpression
262264

263265
switch (sqlUnaryExpression.OperatorType)
264266
{
265-
case ExpressionType.Not when sqlUnaryExpression.Type == typeof(bool):
267+
case ExpressionType.Not
268+
when (sqlUnaryExpression.TypeMapping?.Converter?.ProviderClrType ?? sqlUnaryExpression.Type) == typeof(bool):
266269
{
267270
// when possible, avoid converting to/from predicate form
268271
if (!inSearchConditionContext && 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)