diff --git a/src/EFCore.Cosmos/Extensions/Internal/CosmosExpressionExtensions.cs b/src/EFCore.Cosmos/Extensions/Internal/CosmosExpressionExtensions.cs
new file mode 100644
index 00000000000..e4082679c02
--- /dev/null
+++ b/src/EFCore.Cosmos/Extensions/Internal/CosmosExpressionExtensions.cs
@@ -0,0 +1,29 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System.Linq.Expressions;
+using Microsoft.EntityFrameworkCore.Cosmos.Query.Internal;
+
+// ReSharper disable once CheckNamespace
+namespace Microsoft.EntityFrameworkCore.Internal
+{
+ ///
+ /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
+ /// the same compatibility standards as public APIs. It may be changed or removed without notice in
+ /// any release. You should only use it directly in your code with extreme caution and knowing that
+ /// doing so can result in application failures when updating to a new Entity Framework Core release.
+ ///
+ public static class CosmosExpressionExtensions
+ {
+ ///
+ /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
+ /// the same compatibility standards as public APIs. It may be changed or removed without notice in
+ /// any release. You should only use it directly in your code with extreme caution and knowing that
+ /// doing so can result in application failures when updating to a new Entity Framework Core release.
+ ///
+ public static bool IsLogicalNot(this SqlUnaryExpression sqlUnaryExpression)
+ => sqlUnaryExpression.OperatorType == ExpressionType.Not
+ && (sqlUnaryExpression.Type == typeof(bool)
+ || sqlUnaryExpression.Type == typeof(bool?));
+ }
+}
diff --git a/src/EFCore.Cosmos/Query/Internal/SqlExpressionFactory.cs b/src/EFCore.Cosmos/Query/Internal/SqlExpressionFactory.cs
index a7d0eb86eed..643c8ae1dc5 100644
--- a/src/EFCore.Cosmos/Query/Internal/SqlExpressionFactory.cs
+++ b/src/EFCore.Cosmos/Query/Internal/SqlExpressionFactory.cs
@@ -7,6 +7,7 @@
using System.Linq.Expressions;
using Microsoft.EntityFrameworkCore.Diagnostics;
using Microsoft.EntityFrameworkCore.Infrastructure;
+using Microsoft.EntityFrameworkCore.Internal;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Query;
using Microsoft.EntityFrameworkCore.Storage;
@@ -107,16 +108,20 @@ private SqlExpression ApplyTypeMappingOnSqlUnary(
{
case ExpressionType.Equal:
case ExpressionType.NotEqual:
- case ExpressionType.Not:
+ case ExpressionType.Not
+ when sqlUnaryExpression.IsLogicalNot():
+ {
resultTypeMapping = _boolTypeMapping;
operand = ApplyDefaultTypeMapping(sqlUnaryExpression.Operand);
break;
+ }
case ExpressionType.Convert:
resultTypeMapping = typeMapping;
operand = ApplyDefaultTypeMapping(sqlUnaryExpression.Operand);
break;
+ case ExpressionType.Not:
case ExpressionType.Negate:
resultTypeMapping = typeMapping;
operand = ApplyTypeMapping(sqlUnaryExpression.Operand, typeMapping);
@@ -417,7 +422,7 @@ public virtual SqlUnaryExpression Convert(SqlExpression operand, Type type, Core
/// doing so can result in application failures when updating to a new Entity Framework Core release.
///
public virtual SqlUnaryExpression Not(SqlExpression operand)
- => MakeUnary(ExpressionType.Not, operand, typeof(bool));
+ => MakeUnary(ExpressionType.Not, operand, operand.Type, operand.TypeMapping);
///
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
diff --git a/src/EFCore.Relational/Extensions/Internal/RelationalExpressionExtensions.cs b/src/EFCore.Relational/Extensions/Internal/RelationalExpressionExtensions.cs
new file mode 100644
index 00000000000..24337c302a6
--- /dev/null
+++ b/src/EFCore.Relational/Extensions/Internal/RelationalExpressionExtensions.cs
@@ -0,0 +1,29 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System.Linq.Expressions;
+using Microsoft.EntityFrameworkCore.Query.SqlExpressions;
+
+// ReSharper disable once CheckNamespace
+namespace Microsoft.EntityFrameworkCore.Internal
+{
+ ///
+ /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
+ /// the same compatibility standards as public APIs. It may be changed or removed without notice in
+ /// any release. You should only use it directly in your code with extreme caution and knowing that
+ /// doing so can result in application failures when updating to a new Entity Framework Core release.
+ ///
+ public static class RelationalExpressionExtensions
+ {
+ ///
+ /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
+ /// the same compatibility standards as public APIs. It may be changed or removed without notice in
+ /// any release. You should only use it directly in your code with extreme caution and knowing that
+ /// doing so can result in application failures when updating to a new Entity Framework Core release.
+ ///
+ public static bool IsLogicalNot(this SqlUnaryExpression sqlUnaryExpression)
+ => sqlUnaryExpression.OperatorType == ExpressionType.Not
+ && (sqlUnaryExpression.Type == typeof(bool)
+ || sqlUnaryExpression.Type == typeof(bool?));
+ }
+}
diff --git a/src/EFCore.Relational/Query/Internal/NullSemanticsRewritingExpressionVisitor.cs b/src/EFCore.Relational/Query/Internal/NullSemanticsRewritingExpressionVisitor.cs
index 8497a115581..8313988ee26 100644
--- a/src/EFCore.Relational/Query/Internal/NullSemanticsRewritingExpressionVisitor.cs
+++ b/src/EFCore.Relational/Query/Internal/NullSemanticsRewritingExpressionVisitor.cs
@@ -4,6 +4,7 @@
using System;
using System.Collections.Generic;
using System.Linq.Expressions;
+using Microsoft.EntityFrameworkCore.Internal;
using Microsoft.EntityFrameworkCore.Query.SqlExpressions;
namespace Microsoft.EntityFrameworkCore.Query.Internal
@@ -389,8 +390,8 @@ protected override Expression VisitSqlBinary(SqlBinaryExpression sqlBinaryExpres
var leftUnary = newLeft as SqlUnaryExpression;
var rightUnary = newRight as SqlUnaryExpression;
- var leftNegated = leftUnary?.OperatorType == ExpressionType.Not;
- var rightNegated = rightUnary?.OperatorType == ExpressionType.Not;
+ var leftNegated = leftUnary?.IsLogicalNot() == true;
+ var rightNegated = rightUnary?.IsLogicalNot() == true;
if (leftNegated)
{
diff --git a/src/EFCore.Relational/Query/Internal/SqlExpressionOptimizingExpressionVisitor.cs b/src/EFCore.Relational/Query/Internal/SqlExpressionOptimizingExpressionVisitor.cs
index 47ba1df1242..389fa135338 100644
--- a/src/EFCore.Relational/Query/Internal/SqlExpressionOptimizingExpressionVisitor.cs
+++ b/src/EFCore.Relational/Query/Internal/SqlExpressionOptimizingExpressionVisitor.cs
@@ -113,13 +113,16 @@ private SqlExpression SimplifyUnaryExpression(
{
switch (operatorType)
{
- case ExpressionType.Not:
+ case ExpressionType.Not
+ when type == typeof(bool)
+ || type == typeof(bool?):
+ {
switch (operand)
{
// !(true) -> false
// !(false) -> true
case SqlConstantExpression constantOperand
- when constantOperand.Value is bool value:
+ when constantOperand.Value is bool value:
{
return SqlExpressionFactory.Constant(!value, typeMapping);
}
@@ -142,6 +145,7 @@ private SqlExpression SimplifyUnaryExpression(
case ExpressionType.NotEqual:
return SqlExpressionFactory.IsNull(unaryOperand.Operand);
}
+
break;
case SqlBinaryExpression binaryOperand:
@@ -165,7 +169,8 @@ private SqlExpression SimplifyUnaryExpression(
// those optimizations are only valid in 2-value logic
// they are safe to do here because if we apply null semantics
// because null semantics removes possibility of nulls in the tree when the comparison is wrapped around NOT
- if (!_useRelationalNulls && TryNegate(binaryOperand.OperatorType, out var negated))
+ if (!_useRelationalNulls
+ && TryNegate(binaryOperand.OperatorType, out var negated))
{
return SimplifyBinaryExpression(
negated,
@@ -177,6 +182,7 @@ private SqlExpression SimplifyUnaryExpression(
break;
}
break;
+ }
case ExpressionType.Equal:
case ExpressionType.NotEqual:
diff --git a/src/EFCore.Relational/Query/QuerySqlGenerator.cs b/src/EFCore.Relational/Query/QuerySqlGenerator.cs
index 4133df125a6..2dbb0c227fb 100644
--- a/src/EFCore.Relational/Query/QuerySqlGenerator.cs
+++ b/src/EFCore.Relational/Query/QuerySqlGenerator.cs
@@ -7,6 +7,7 @@
using System.Linq.Expressions;
using System.Text.RegularExpressions;
using Microsoft.EntityFrameworkCore.Diagnostics;
+using Microsoft.EntityFrameworkCore.Internal;
using Microsoft.EntityFrameworkCore.Query.SqlExpressions;
using Microsoft.EntityFrameworkCore.Storage;
using Microsoft.EntityFrameworkCore.Storage.Internal;
@@ -515,7 +516,8 @@ protected override Expression VisitSqlUnary(SqlUnaryExpression sqlUnaryExpressio
break;
}
- case ExpressionType.Not:
+ case ExpressionType.Not
+ when sqlUnaryExpression.IsLogicalNot():
{
_relationalCommandBuilder.Append("NOT (");
Visit(sqlUnaryExpression.Operand);
@@ -523,6 +525,13 @@ protected override Expression VisitSqlUnary(SqlUnaryExpression sqlUnaryExpressio
break;
}
+ case ExpressionType.Not:
+ {
+ _relationalCommandBuilder.Append("~");
+ Visit(sqlUnaryExpression.Operand);
+ break;
+ }
+
case ExpressionType.Equal:
{
Visit(sqlUnaryExpression.Operand);
diff --git a/src/EFCore.Relational/Query/SqlExpressionFactory.cs b/src/EFCore.Relational/Query/SqlExpressionFactory.cs
index e84f8e6d66c..e72aadf325e 100644
--- a/src/EFCore.Relational/Query/SqlExpressionFactory.cs
+++ b/src/EFCore.Relational/Query/SqlExpressionFactory.cs
@@ -6,6 +6,7 @@
using System.Linq;
using System.Linq.Expressions;
using Microsoft.EntityFrameworkCore.Infrastructure;
+using Microsoft.EntityFrameworkCore.Internal;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Query.SqlExpressions;
using Microsoft.EntityFrameworkCore.Storage;
@@ -105,16 +106,20 @@ private SqlExpression ApplyTypeMappingOnSqlUnary(
{
case ExpressionType.Equal:
case ExpressionType.NotEqual:
- case ExpressionType.Not:
+ case ExpressionType.Not
+ when sqlUnaryExpression.IsLogicalNot():
+ {
resultTypeMapping = _boolTypeMapping;
operand = ApplyDefaultTypeMapping(sqlUnaryExpression.Operand);
break;
+ }
case ExpressionType.Convert:
resultTypeMapping = typeMapping;
operand = ApplyDefaultTypeMapping(sqlUnaryExpression.Operand);
break;
+ case ExpressionType.Not:
case ExpressionType.Negate:
resultTypeMapping = typeMapping;
operand = ApplyTypeMapping(sqlUnaryExpression.Operand, typeMapping);
@@ -283,7 +288,7 @@ public virtual SqlUnaryExpression Convert(SqlExpression operand, Type type, Rela
=> MakeUnary(ExpressionType.Convert, operand, type, typeMapping);
public virtual SqlUnaryExpression Not(SqlExpression operand)
- => MakeUnary(ExpressionType.Not, operand, typeof(bool));
+ => MakeUnary(ExpressionType.Not, operand, operand.Type, operand.TypeMapping);
public virtual SqlUnaryExpression Negate(SqlExpression operand)
=> MakeUnary(ExpressionType.Negate, operand, operand.Type, operand.TypeMapping);
diff --git a/src/EFCore.SqlServer/Query/Internal/SearchConditionConvertingExpressionVisitor.cs b/src/EFCore.SqlServer/Query/Internal/SearchConditionConvertingExpressionVisitor.cs
index 7736fbe01a3..c206cc7ffe5 100644
--- a/src/EFCore.SqlServer/Query/Internal/SearchConditionConvertingExpressionVisitor.cs
+++ b/src/EFCore.SqlServer/Query/Internal/SearchConditionConvertingExpressionVisitor.cs
@@ -4,6 +4,7 @@
using System;
using System.Collections.Generic;
using System.Linq.Expressions;
+using Microsoft.EntityFrameworkCore.Internal;
using Microsoft.EntityFrameworkCore.Query;
using Microsoft.EntityFrameworkCore.Query.SqlExpressions;
@@ -220,10 +221,18 @@ protected override Expression VisitSqlUnary(SqlUnaryExpression sqlUnaryExpressio
bool resultCondition;
switch (sqlUnaryExpression.OperatorType)
{
- case ExpressionType.Not:
+ case ExpressionType.Not
+ when sqlUnaryExpression.IsLogicalNot():
+ {
_isSearchCondition = true;
resultCondition = true;
break;
+ }
+
+ case ExpressionType.Not:
+ _isSearchCondition = false;
+ resultCondition = false;
+ break;
case ExpressionType.Convert:
case ExpressionType.Negate:
diff --git a/src/EFCore/Extensions/Internal/ExpressionExtensions.cs b/src/EFCore/Extensions/Internal/ExpressionExtensions.cs
index f7920138b7f..df151fdd18d 100644
--- a/src/EFCore/Extensions/Internal/ExpressionExtensions.cs
+++ b/src/EFCore/Extensions/Internal/ExpressionExtensions.cs
@@ -155,6 +155,17 @@ public static LambdaExpression GetLambdaOrNull(this Expression expression)
? (LambdaExpression)unary.Operand
: null;
+ ///
+ /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
+ /// the same compatibility standards as public APIs. It may be changed or removed without notice in
+ /// any release. You should only use it directly in your code with extreme caution and knowing that
+ /// doing so can result in application failures when updating to a new Entity Framework Core release.
+ ///
+ public static bool IsLogicalNot(this UnaryExpression sqlUnaryExpression)
+ => sqlUnaryExpression.NodeType == ExpressionType.Not
+ && (sqlUnaryExpression.Type == typeof(bool)
+ || sqlUnaryExpression.Type == typeof(bool?));
+
private static Expression RemoveConvert(Expression expression)
{
if (expression is UnaryExpression unaryExpression
diff --git a/src/EFCore/Query/Internal/AllAnyToContainsRewritingExpressionVisitor.cs b/src/EFCore/Query/Internal/AllAnyToContainsRewritingExpressionVisitor.cs
index 7a5aecafdd2..173c25741ed 100644
--- a/src/EFCore/Query/Internal/AllAnyToContainsRewritingExpressionVisitor.cs
+++ b/src/EFCore/Query/Internal/AllAnyToContainsRewritingExpressionVisitor.cs
@@ -4,6 +4,7 @@
using System;
using System.Linq.Expressions;
using System.Reflection;
+using Microsoft.EntityFrameworkCore.Internal;
namespace Microsoft.EntityFrameworkCore.Query.Internal
{
@@ -67,6 +68,7 @@ private bool TryExtractEqualityOperands(Expression expression, out Expression le
case MethodCallExpression methodCallExpression
when methodCallExpression.Method.Name == nameof(object.Equals):
+ {
negated = false;
if (methodCallExpression.Arguments.Count == 1
&& methodCallExpression.Object.Type == methodCallExpression.Arguments[0].Type)
@@ -80,12 +82,15 @@ private bool TryExtractEqualityOperands(Expression expression, out Expression le
}
return true;
+ }
case UnaryExpression unaryExpression
- when unaryExpression.NodeType == ExpressionType.Not:
+ when unaryExpression.IsLogicalNot():
+ {
var result = TryExtractEqualityOperands(unaryExpression.Operand, out left, out right, out negated);
negated = !negated;
return result;
+ }
}
return false;
diff --git a/src/EFCore/Query/Internal/NegationOptimizingExpressionVisitor.cs b/src/EFCore/Query/Internal/NegationOptimizingExpressionVisitor.cs
index efae0d840d7..3aaf1eee74b 100644
--- a/src/EFCore/Query/Internal/NegationOptimizingExpressionVisitor.cs
+++ b/src/EFCore/Query/Internal/NegationOptimizingExpressionVisitor.cs
@@ -3,6 +3,7 @@
using System.Collections.Generic;
using System.Linq.Expressions;
+using Microsoft.EntityFrameworkCore.Internal;
namespace Microsoft.EntityFrameworkCore.Query.Internal
{
@@ -32,7 +33,8 @@ private static ExpressionType Negate(ExpressionType expressionType)
protected override Expression VisitUnary(UnaryExpression unaryExpression)
{
- if (unaryExpression.NodeType == ExpressionType.Not)
+ if (unaryExpression.NodeType == ExpressionType.Not
+ && unaryExpression.IsLogicalNot())
{
if (unaryExpression.Operand is ConstantExpression innerConstant
&& innerConstant.Value is bool value)
diff --git a/test/EFCore.Cosmos.FunctionalTests/Query/SimpleQueryCosmosTest.cs b/test/EFCore.Cosmos.FunctionalTests/Query/SimpleQueryCosmosTest.cs
index 0cc86a724d2..d48dfd5c7fc 100644
--- a/test/EFCore.Cosmos.FunctionalTests/Query/SimpleQueryCosmosTest.cs
+++ b/test/EFCore.Cosmos.FunctionalTests/Query/SimpleQueryCosmosTest.cs
@@ -2342,6 +2342,38 @@ FROM root c
WHERE ((c[""Discriminator""] = ""Customer"") AND (((c[""CustomerID""] = ""ALFKI"") & (c[""CustomerID""] = ""ANATR"")) OR (c[""CustomerID""] = ""ANTON"")))");
}
+ public override async Task Where_bitwise_binary_not(bool isAsync)
+ {
+ await base.Where_bitwise_binary_not(isAsync);
+
+ AssertSql(
+ @"@__negatedId_0='-10249'
+
+SELECT c
+FROM root c
+WHERE ((c[""Discriminator""] = ""Customer"") AND (~c[""CustomerID""] = @__negatedId_0)");
+ }
+
+ public override async Task Where_bitwise_binary_and(bool isAsync)
+ {
+ await base.Where_bitwise_binary_and(isAsync);
+
+ AssertSql(
+ @"SELECT c
+FROM root c
+WHERE ((c[""Discriminator""] = ""Customer"") AND ((c[""CustomerID""] & 10248) = 10248)");
+ }
+
+ public override async Task Where_bitwise_binary_or(bool isAsync)
+ {
+ await base.Where_bitwise_binary_or(isAsync);
+
+ AssertSql(
+ @"SELECT c
+FROM root c
+WHERE ((c[""Discriminator""] = ""Customer"") AND ((c[""CustomerID""] | 10248) = 10248)");
+ }
+
[ConditionalFact(Skip = "Issue #17246")]
public override void Select_bitwise_or_with_logical_or()
{
diff --git a/test/EFCore.Specification.Tests/Query/SimpleQueryTestBase.cs b/test/EFCore.Specification.Tests/Query/SimpleQueryTestBase.cs
index 9d8c46d0dfa..434331fed24 100644
--- a/test/EFCore.Specification.Tests/Query/SimpleQueryTestBase.cs
+++ b/test/EFCore.Specification.Tests/Query/SimpleQueryTestBase.cs
@@ -3764,6 +3764,38 @@ public virtual Task Where_bitwise_and_with_logical_or(bool isAsync)
entryCount: 1);
}
+ [ConditionalTheory]
+ [MemberData(nameof(IsAsyncData))]
+ public virtual Task Where_bitwise_binary_not(bool isAsync)
+ {
+ var negatedId = ~10248;
+
+ return AssertQuery(
+ isAsync,
+ ss => ss.Set().Where(o => ~o.OrderID == negatedId),
+ entryCount: 1);
+ }
+
+ [ConditionalTheory]
+ [MemberData(nameof(IsAsyncData))]
+ public virtual Task Where_bitwise_binary_and(bool isAsync)
+ {
+ return AssertQuery(
+ isAsync,
+ ss => ss.Set().Where(o => (o.OrderID & 10248) == 10248),
+ entryCount: 416);
+ }
+
+ [ConditionalTheory]
+ [MemberData(nameof(IsAsyncData))]
+ public virtual Task Where_bitwise_binary_or(bool isAsync)
+ {
+ return AssertQuery(
+ isAsync,
+ ss => ss.Set().Where(o => (o.OrderID | 10248) == 10248),
+ entryCount: 1);
+ }
+
[ConditionalFact]
public virtual void Select_bitwise_or_with_logical_or()
{
diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/SimpleQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/SimpleQuerySqlServerTest.cs
index 3aa2642e70e..06906ffd802 100644
--- a/test/EFCore.SqlServer.FunctionalTests/Query/SimpleQuerySqlServerTest.cs
+++ b/test/EFCore.SqlServer.FunctionalTests/Query/SimpleQuerySqlServerTest.cs
@@ -2887,6 +2887,38 @@ ELSE CAST(0 AS bit)
END) = CAST(1 AS bit)) OR ([c].[CustomerID] = N'ANTON')");
}
+ public override async Task Where_bitwise_binary_not(bool isAsync)
+ {
+ await base.Where_bitwise_binary_not(isAsync);
+
+ AssertSql(
+ @"@__negatedId_0='-10249'
+
+SELECT [o].[OrderID], [o].[CustomerID], [o].[EmployeeID], [o].[OrderDate]
+FROM [Orders] AS [o]
+WHERE ~[o].[OrderID] = @__negatedId_0");
+ }
+
+ public override async Task Where_bitwise_binary_and(bool isAsync)
+ {
+ await base.Where_bitwise_binary_and(isAsync);
+
+ AssertSql(
+ @"SELECT [o].[OrderID], [o].[CustomerID], [o].[EmployeeID], [o].[OrderDate]
+FROM [Orders] AS [o]
+WHERE ([o].[OrderID] & 10248) = 10248");
+ }
+
+ public override async Task Where_bitwise_binary_or(bool isAsync)
+ {
+ await base.Where_bitwise_binary_or(isAsync);
+
+ AssertSql(
+ @"SELECT [o].[OrderID], [o].[CustomerID], [o].[EmployeeID], [o].[OrderDate]
+FROM [Orders] AS [o]
+WHERE ([o].[OrderID] | 10248) = 10248");
+ }
+
public override void Select_bitwise_or_with_logical_or()
{
base.Select_bitwise_or_with_logical_or();