diff --git a/src/EFCore.Relational/Query/SqlNullabilityProcessor.cs b/src/EFCore.Relational/Query/SqlNullabilityProcessor.cs index 988e62e9ad0..c8fb53f61cf 100644 --- a/src/EFCore.Relational/Query/SqlNullabilityProcessor.cs +++ b/src/EFCore.Relational/Query/SqlNullabilityProcessor.cs @@ -2032,7 +2032,30 @@ protected virtual SqlExpression OptimizeNonNullableNotExpression(SqlUnaryExpress sqlBinaryOperand.TypeMapping)!; } } - break; + break; + + case CaseExpression caseExpression: + { + if (caseExpression.Type == typeof(bool) + && caseExpression.ElseResult is SqlConstantExpression elseResult + && caseExpression.WhenClauses.All(clause => clause.Result is SqlConstantExpression) + ) + { + var clauses = caseExpression.WhenClauses + .Select(clause => new CaseWhenClause( + clause.Test, + _sqlExpressionFactory.Constant(!(bool)(clause.Result as SqlConstantExpression)!.Value!, clause.Result.TypeMapping)) + ) + .ToList(); + var newElseResult = _sqlExpressionFactory.Constant(!(bool)elseResult.Value!, elseResult.TypeMapping); + + return caseExpression.Operand is null + ? _sqlExpressionFactory.Case(clauses, newElseResult) + : _sqlExpressionFactory.Case(caseExpression.Operand, clauses, newElseResult); + } + } + + break; } return sqlUnaryExpression; @@ -2369,7 +2392,7 @@ private SqlExpression ProcessNullNotNull(SqlUnaryExpression sqlUnaryExpression, return result; } } - break; + break; } return sqlUnaryExpression; diff --git a/test/EFCore.Cosmos.FunctionalTests/Query/NorthwindWhereQueryCosmosTest.cs b/test/EFCore.Cosmos.FunctionalTests/Query/NorthwindWhereQueryCosmosTest.cs index 9193055635d..7b339e1b598 100644 --- a/test/EFCore.Cosmos.FunctionalTests/Query/NorthwindWhereQueryCosmosTest.cs +++ b/test/EFCore.Cosmos.FunctionalTests/Query/NorthwindWhereQueryCosmosTest.cs @@ -2100,6 +2100,20 @@ public override Task Where_ternary_boolean_condition_with_false_as_result_false( SELECT c FROM root c WHERE ((c["Discriminator"] = "Product") AND false) +"""); + }); + + public override Task Where_ternary_boolean_condition_negated(bool async) + => Fixture.NoSyncTest( + async, async a => + { + await base.Where_ternary_boolean_condition_negated(a); + + AssertSql( + """ +SELECT c +FROM root c +WHERE ((c["Discriminator"] = "Product") AND NOT(((c["UnitsInStock"] >= 20) ? false : true))) """); }); diff --git a/test/EFCore.Specification.Tests/Query/NorthwindWhereQueryTestBase.cs b/test/EFCore.Specification.Tests/Query/NorthwindWhereQueryTestBase.cs index 5805fb6fa45..a50dd017052 100644 --- a/test/EFCore.Specification.Tests/Query/NorthwindWhereQueryTestBase.cs +++ b/test/EFCore.Specification.Tests/Query/NorthwindWhereQueryTestBase.cs @@ -1465,6 +1465,15 @@ public virtual Task Where_ternary_boolean_condition_with_false_as_result_false(b assertEmpty: true); } + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Where_ternary_boolean_condition_negated(bool async) + { + return AssertQuery( + async, + ss => ss.Set().Where(p => !(p.UnitsInStock >= 20 ? false : true))); + } + [ConditionalTheory] [MemberData(nameof(IsAsyncData))] public virtual Task Where_compare_constructed_equal(bool async) diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/ComplexNavigationsQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/ComplexNavigationsQuerySqlServerTest.cs index 76a2eae5416..45d923f908e 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/ComplexNavigationsQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/ComplexNavigationsQuerySqlServerTest.cs @@ -4127,9 +4127,9 @@ FROM [LevelTwo] AS [l0] ) AS [l1] GROUP BY [l1].[Key] ) AS [l2] ON [l].[Id] = [l2].[Key] AND CASE - WHEN [l2].[Sum] <= 10 THEN CAST(1 AS bit) - ELSE CAST(0 AS bit) -END = CAST(0 AS bit) + WHEN [l2].[Sum] <= 10 THEN CAST(0 AS bit) + ELSE CAST(1 AS bit) +END = CAST(1 AS bit) """); } diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/ComplexNavigationsSharedTypeQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/ComplexNavigationsSharedTypeQuerySqlServerTest.cs index bc8b3af0b95..fb0cc9ffd47 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/ComplexNavigationsSharedTypeQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/ComplexNavigationsSharedTypeQuerySqlServerTest.cs @@ -975,9 +975,9 @@ WHERE [l2].[OneToOne_Required_PK_Date] IS NOT NULL AND [l2].[Level1_Required_Id] ) AS [s] GROUP BY [s].[Key] ) AS [s1] ON [l].[Id] = [s1].[Key] AND CASE - WHEN [s1].[Sum] <= 10 THEN CAST(1 AS bit) - ELSE CAST(0 AS bit) -END = CAST(0 AS bit) + WHEN [s1].[Sum] <= 10 THEN CAST(0 AS bit) + ELSE CAST(1 AS bit) +END = CAST(1 AS bit) """); } diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindWhereQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindWhereQuerySqlServerTest.cs index 5b4c0ba2e7f..1ba0c1ad5a3 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindWhereQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindWhereQuerySqlServerTest.cs @@ -1706,6 +1706,21 @@ FROM [Products] AS [p] """); } + public override async Task Where_ternary_boolean_condition_negated(bool async) + { + await base.Where_ternary_boolean_condition_negated(async); + + AssertSql( + """ +SELECT [p].[ProductID], [p].[Discontinued], [p].[ProductName], [p].[SupplierID], [p].[UnitPrice], [p].[UnitsInStock] +FROM [Products] AS [p] +WHERE CASE + WHEN [p].[UnitsInStock] >= CAST(20 AS smallint) THEN CAST(1 AS bit) + ELSE CAST(0 AS bit) +END = CAST(1 AS bit) +"""); + } + public override async Task Where_compare_constructed_equal(bool async) { // Anonymous type to constant comparison. Issue #14672. diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/NullSemanticsQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/NullSemanticsQuerySqlServerTest.cs index f49e1126ef5..8bb7449f6e5 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/NullSemanticsQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/NullSemanticsQuerySqlServerTest.cs @@ -2543,9 +2543,9 @@ public override async Task Negated_order_comparison_on_nullable_arguments_doesnt SELECT [e].[Id] FROM [Entities1] AS [e] WHERE CASE - WHEN [e].[NullableIntA] > @__i_0 THEN CAST(1 AS bit) - ELSE CAST(0 AS bit) -END = CAST(0 AS bit) + WHEN [e].[NullableIntA] > @__i_0 THEN CAST(0 AS bit) + ELSE CAST(1 AS bit) +END = CAST(1 AS bit) """, // """ @@ -2554,9 +2554,9 @@ ELSE CAST(0 AS bit) SELECT [e].[Id] FROM [Entities1] AS [e] WHERE CASE - WHEN [e].[NullableIntA] >= @__i_0 THEN CAST(1 AS bit) - ELSE CAST(0 AS bit) -END = CAST(0 AS bit) + WHEN [e].[NullableIntA] >= @__i_0 THEN CAST(0 AS bit) + ELSE CAST(1 AS bit) +END = CAST(1 AS bit) """, // """ @@ -2565,9 +2565,9 @@ ELSE CAST(0 AS bit) SELECT [e].[Id] FROM [Entities1] AS [e] WHERE CASE - WHEN [e].[NullableIntA] < @__i_0 THEN CAST(1 AS bit) - ELSE CAST(0 AS bit) -END = CAST(0 AS bit) + WHEN [e].[NullableIntA] < @__i_0 THEN CAST(0 AS bit) + ELSE CAST(1 AS bit) +END = CAST(1 AS bit) """, // """ @@ -2576,9 +2576,9 @@ ELSE CAST(0 AS bit) SELECT [e].[Id] FROM [Entities1] AS [e] WHERE CASE - WHEN [e].[NullableIntA] <= @__i_0 THEN CAST(1 AS bit) - ELSE CAST(0 AS bit) -END = CAST(0 AS bit) + WHEN [e].[NullableIntA] <= @__i_0 THEN CAST(0 AS bit) + ELSE CAST(1 AS bit) +END = CAST(1 AS bit) """); } diff --git a/test/EFCore.Sqlite.FunctionalTests/Query/NorthwindFunctionsQuerySqliteTest.cs b/test/EFCore.Sqlite.FunctionalTests/Query/NorthwindFunctionsQuerySqliteTest.cs index 46b2730852d..b5552e1a602 100644 --- a/test/EFCore.Sqlite.FunctionalTests/Query/NorthwindFunctionsQuerySqliteTest.cs +++ b/test/EFCore.Sqlite.FunctionalTests/Query/NorthwindFunctionsQuerySqliteTest.cs @@ -878,10 +878,10 @@ public override async Task String_Contains_negated_in_predicate(bool async) """ SELECT "c"."CustomerID", "c"."Address", "c"."City", "c"."CompanyName", "c"."ContactName", "c"."ContactTitle", "c"."Country", "c"."Fax", "c"."Phone", "c"."PostalCode", "c"."Region" FROM "Customers" AS "c" -WHERE NOT (CASE - WHEN instr("c"."CompanyName", "c"."ContactName") > 0 THEN 1 - ELSE 0 -END) +WHERE CASE + WHEN instr("c"."CompanyName", "c"."ContactName") > 0 THEN 0 + ELSE 1 +END """); } @@ -891,10 +891,10 @@ public override async Task String_Contains_negated_in_projection(bool async) AssertSql( """ -SELECT "c"."CustomerID" AS "Id", NOT (CASE - WHEN instr("c"."CompanyName", "c"."ContactName") > 0 THEN 1 - ELSE 0 -END) AS "Value" +SELECT "c"."CustomerID" AS "Id", CASE + WHEN instr("c"."CompanyName", "c"."ContactName") > 0 THEN 0 + ELSE 1 +END AS "Value" FROM "Customers" AS "c" """); } diff --git a/test/EFCore.Sqlite.FunctionalTests/Query/NorthwindWhereQuerySqliteTest.cs b/test/EFCore.Sqlite.FunctionalTests/Query/NorthwindWhereQuerySqliteTest.cs index 0c225b48f72..31a2155adb1 100644 --- a/test/EFCore.Sqlite.FunctionalTests/Query/NorthwindWhereQuerySqliteTest.cs +++ b/test/EFCore.Sqlite.FunctionalTests/Query/NorthwindWhereQuerySqliteTest.cs @@ -14,6 +14,21 @@ public NorthwindWhereQuerySqliteTest(NorthwindQuerySqliteFixture= 20 THEN 1 + ELSE 0 +END +"""); + } + public override Task Where_datetimeoffset_now_component(bool async) => AssertTranslationFailed(() => base.Where_datetimeoffset_now_component(async)); diff --git a/test/EFCore.Sqlite.FunctionalTests/Query/NullSemanticsQuerySqliteTest.cs b/test/EFCore.Sqlite.FunctionalTests/Query/NullSemanticsQuerySqliteTest.cs index 4daaae16702..d5e16911667 100644 --- a/test/EFCore.Sqlite.FunctionalTests/Query/NullSemanticsQuerySqliteTest.cs +++ b/test/EFCore.Sqlite.FunctionalTests/Query/NullSemanticsQuerySqliteTest.cs @@ -475,10 +475,10 @@ public override async Task Negated_order_comparison_on_nullable_arguments_doesnt SELECT "e"."Id" FROM "Entities1" AS "e" -WHERE NOT (CASE - WHEN "e"."NullableIntA" > @__i_0 THEN 1 - ELSE 0 -END) +WHERE CASE + WHEN "e"."NullableIntA" > @__i_0 THEN 0 + ELSE 1 +END """, // """ @@ -486,10 +486,10 @@ ELSE 0 SELECT "e"."Id" FROM "Entities1" AS "e" -WHERE NOT (CASE - WHEN "e"."NullableIntA" >= @__i_0 THEN 1 - ELSE 0 -END) +WHERE CASE + WHEN "e"."NullableIntA" >= @__i_0 THEN 0 + ELSE 1 +END """, // """ @@ -497,10 +497,10 @@ ELSE 0 SELECT "e"."Id" FROM "Entities1" AS "e" -WHERE NOT (CASE - WHEN "e"."NullableIntA" < @__i_0 THEN 1 - ELSE 0 -END) +WHERE CASE + WHEN "e"."NullableIntA" < @__i_0 THEN 0 + ELSE 1 +END """, // """ @@ -508,10 +508,10 @@ ELSE 0 SELECT "e"."Id" FROM "Entities1" AS "e" -WHERE NOT (CASE - WHEN "e"."NullableIntA" <= @__i_0 THEN 1 - ELSE 0 -END) +WHERE CASE + WHEN "e"."NullableIntA" <= @__i_0 THEN 0 + ELSE 1 +END """); }