diff --git a/src/EFCore.Relational/Query/QuerySqlGenerator.cs b/src/EFCore.Relational/Query/QuerySqlGenerator.cs index d41c2245c3a..28275b38982 100644 --- a/src/EFCore.Relational/Query/QuerySqlGenerator.cs +++ b/src/EFCore.Relational/Query/QuerySqlGenerator.cs @@ -676,20 +676,56 @@ protected override Expression VisitSqlUnary(SqlUnaryExpression sqlUnaryExpressio case ExpressionType.Not: { _relationalCommandBuilder.Append("~"); + + var requiresBrackets = RequiresParentheses(sqlUnaryExpression, sqlUnaryExpression.Operand); + if (requiresBrackets) + { + _relationalCommandBuilder.Append("("); + } + Visit(sqlUnaryExpression.Operand); + if (requiresBrackets) + { + _relationalCommandBuilder.Append(")"); + } + break; } case ExpressionType.Equal: { + + var requiresBrackets = RequiresParentheses(sqlUnaryExpression, sqlUnaryExpression.Operand); + if (requiresBrackets) + { + _relationalCommandBuilder.Append("("); + } + Visit(sqlUnaryExpression.Operand); + if (requiresBrackets) + { + _relationalCommandBuilder.Append(")"); + } + _relationalCommandBuilder.Append(" IS NULL"); break; } case ExpressionType.NotEqual: { + + var requiresBrackets = RequiresParentheses(sqlUnaryExpression, sqlUnaryExpression.Operand); + if (requiresBrackets) + { + _relationalCommandBuilder.Append("("); + } + Visit(sqlUnaryExpression.Operand); + if (requiresBrackets) + { + _relationalCommandBuilder.Append(")"); + } + _relationalCommandBuilder.Append(" IS NOT NULL"); break; } @@ -799,6 +835,13 @@ protected virtual bool RequiresParentheses(SqlExpression outerExpression, SqlExp return true; } + if (sqlUnaryExpression.OperatorType == ExpressionType.Negate + && outerExpression is SqlUnaryExpression { OperatorType: ExpressionType.Negate }) + { + // double negative sign is interpreted as a comment in SQL, so we need to enclose it in brackets + return true; + } + return false; } diff --git a/test/EFCore.Specification.Tests/Query/GearsOfWarQueryTestBase.cs b/test/EFCore.Specification.Tests/Query/GearsOfWarQueryTestBase.cs index f60fd4747a0..b2f7826c981 100644 --- a/test/EFCore.Specification.Tests/Query/GearsOfWarQueryTestBase.cs +++ b/test/EFCore.Specification.Tests/Query/GearsOfWarQueryTestBase.cs @@ -56,6 +56,15 @@ public virtual Task Negate_on_column(bool async) async, ss => ss.Set().Where(s => s.Id == -s.Id)); + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Double_negate_on_column(bool async) + { + return AssertQuery( + async, + ss => ss.Set().Where(s => -(-s.Id) == s.Id)); + } + [ConditionalTheory] [MemberData(nameof(IsAsyncData))] public virtual Task Negate_on_like_expression(bool async) diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/GearsOfWarQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/GearsOfWarQuerySqlServerTest.cs index cc7c2bd0eee..d24c9c7cb8f 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/GearsOfWarQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/GearsOfWarQuerySqlServerTest.cs @@ -44,6 +44,16 @@ FROM [Squads] AS [s] WHERE [s].[Id] = -[s].[Id]"); } + public override async Task Double_negate_on_column(bool async) + { + await base.Double_negate_on_column(async); + + AssertSql( + @"SELECT [s].[Id], [s].[Banner], [s].[Banner5], [s].[InternalNumber], [s].[Name] +FROM [Squads] AS [s] +WHERE -(-[s].[Id]) = [s].[Id]"); + } + public override async Task Negate_on_like_expression(bool async) { await base.Negate_on_like_expression(async); diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/TPTGearsOfWarQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/TPTGearsOfWarQuerySqlServerTest.cs index c60e4062998..a432d5c6604 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/TPTGearsOfWarQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/TPTGearsOfWarQuerySqlServerTest.cs @@ -43,6 +43,16 @@ FROM [Squads] AS [s] WHERE [s].[Id] = -[s].[Id]"); } + public override async Task Double_negate_on_column(bool async) + { + await base.Double_negate_on_column(async); + + AssertSql( + @"SELECT [s].[Id], [s].[Banner], [s].[Banner5], [s].[InternalNumber], [s].[Name] +FROM [Squads] AS [s] +WHERE -(-[s].[Id]) = [s].[Id]"); + } + public override async Task Negate_on_like_expression(bool async) { await base.Negate_on_like_expression(async);