diff --git a/src/EFCore.Relational/Query/SqlNullabilityProcessor.cs b/src/EFCore.Relational/Query/SqlNullabilityProcessor.cs index 07f7c9297f7..1a0777b8379 100644 --- a/src/EFCore.Relational/Query/SqlNullabilityProcessor.cs +++ b/src/EFCore.Relational/Query/SqlNullabilityProcessor.cs @@ -1174,6 +1174,29 @@ protected virtual SqlExpression VisitSqlBinary( bool allowOptimizedExpansion, out bool nullable) { + // Most optimizations are done in OptimizeComparison below, but this one + // benefits from being done early. + // Consider query: (x.NullableString == "Foo") == true + // We recursively visit Left and Right, but when processing the left + // side, allowOptimizedExpansion would be set to false (we only allow it + // to trickle down to child nodes for AndAlso & OrElse operations), so + // the comparison would get unnecessarily expanded. In order to avoid + // this, we would need to modify the allowOptimizedExpansion calculation + // to capture this scenario and then flow allowOptimizedExpansion to + // OptimizeComparison. Instead, we just do the optimization right away + // and the resulting code is clearer. + if (allowOptimizedExpansion && sqlBinaryExpression.OperatorType == ExpressionType.Equal) + { + if (IsTrue(sqlBinaryExpression.Left) && sqlBinaryExpression.Left.TypeMapping!.Converter == null) + { + return Visit(sqlBinaryExpression.Right, allowOptimizedExpansion, out nullable); + } + else if (IsTrue(sqlBinaryExpression.Right) && sqlBinaryExpression.Right.TypeMapping!.Converter == null) + { + return Visit(sqlBinaryExpression.Left, allowOptimizedExpansion, out nullable); + } + } + var optimize = allowOptimizedExpansion; allowOptimizedExpansion = allowOptimizedExpansion diff --git a/test/EFCore.Relational.Specification.Tests/Query/NullSemanticsQueryTestBase.cs b/test/EFCore.Relational.Specification.Tests/Query/NullSemanticsQueryTestBase.cs index 9c512a44cbc..ae9b5725c84 100644 --- a/test/EFCore.Relational.Specification.Tests/Query/NullSemanticsQueryTestBase.cs +++ b/test/EFCore.Relational.Specification.Tests/Query/NullSemanticsQueryTestBase.cs @@ -402,6 +402,23 @@ join e2 in ss.Set() on e1.NullableIntA equals e2.NullableI }, elementSorter: e => (e.Id1, e.Id2)); + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Join_uses_csharp_semantics_for_anon_objects(bool async) + => AssertQuery( + async, + ss => from e1 in ss.Set() + join e2 in ss.Set() on + new { NullInt = e1.NullableIntA } equals new { NullInt = e2.NullableIntB } + select new + { + Id1 = e1.Id, + Id2 = e2.Id, + e1.NullableIntA, + e2.NullableIntB + }, + elementSorter: e => (e.Id1, e.Id2)); + [ConditionalTheory] [MemberData(nameof(IsAsyncData))] public virtual Task Contains_with_local_array_closure_with_null(bool async) diff --git a/test/EFCore.Specification.Tests/Query/ComplexNavigationsQueryTestBase.cs b/test/EFCore.Specification.Tests/Query/ComplexNavigationsQueryTestBase.cs index 8cc5fee20d5..df07e508353 100644 --- a/test/EFCore.Specification.Tests/Query/ComplexNavigationsQueryTestBase.cs +++ b/test/EFCore.Specification.Tests/Query/ComplexNavigationsQueryTestBase.cs @@ -3445,6 +3445,22 @@ public virtual Task Composite_key_join_on_groupby_aggregate_projecting_only_grou }, (o, i) => i.Key)); + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Composite_key_join_on_groupby_aggregate_projecting_only_grouping_key2(bool async) + => AssertQueryScalar( + async, + ss => ss.Set() + .Join( + ss.Set().GroupBy(g => g.Id % 3).Select(g => new { g.Key, Sum = g.Sum(x => x.Id) }), + o => new { o.Id, Condition = false }, + i => new + { + Id = i.Key, + Condition = i.Sum <= 10, + }, + (o, i) => i.Key)); + [ConditionalTheory] [MemberData(nameof(IsAsyncData))] public virtual Task Multiple_joins_groupby_predicate(bool async) diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/ComplexNavigationsQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/ComplexNavigationsQuerySqlServerTest.cs index 762d51aa38a..fbac77b43ee 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/ComplexNavigationsQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/ComplexNavigationsQuerySqlServerTest.cs @@ -4107,8 +4107,27 @@ INNER JOIN ( FROM [LevelTwo] AS [l0] ) AS [l1] GROUP BY [l1].[Key] -) AS [l2] ON [l].[Id] = [l2].[Key] AND CAST(1 AS bit) = CASE - WHEN [l2].[Sum] > 10 THEN CAST(1 AS bit) +) AS [l2] ON [l].[Id] = [l2].[Key] AND [l2].[Sum] > 10 +"""); + } + + public override async Task Composite_key_join_on_groupby_aggregate_projecting_only_grouping_key2(bool async) + { + await base.Composite_key_join_on_groupby_aggregate_projecting_only_grouping_key2(async); + + AssertSql( + """ +SELECT [l2].[Key] +FROM [LevelOne] AS [l] +INNER JOIN ( + SELECT [l1].[Key], COALESCE(SUM([l1].[Id]), 0) AS [Sum] + FROM ( + SELECT [l0].[Id], [l0].[Id] % 3 AS [Key] + FROM [LevelTwo] AS [l0] + ) AS [l1] + GROUP BY [l1].[Key] +) AS [l2] ON [l].[Id] = [l2].[Key] AND CAST(0 AS bit) = CASE + WHEN [l2].[Sum] <= 10 THEN CAST(1 AS bit) ELSE CAST(0 AS bit) END """); diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/ComplexNavigationsSharedTypeQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/ComplexNavigationsSharedTypeQuerySqlServerTest.cs index e686a5035b7..8d1e5c892f7 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/ComplexNavigationsSharedTypeQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/ComplexNavigationsSharedTypeQuerySqlServerTest.cs @@ -920,8 +920,62 @@ WHEN [l2].[OneToOne_Required_PK_Date] IS NOT NULL AND [l2].[Level1_Required_Id] WHERE [l2].[OneToOne_Required_PK_Date] IS NOT NULL AND [l2].[Level1_Required_Id] IS NOT NULL AND [l2].[OneToMany_Required_Inverse2Id] IS NOT NULL ) AS [s] GROUP BY [s].[Key] -) AS [s1] ON [l].[Id] = [s1].[Key] AND CAST(1 AS bit) = CASE - WHEN [s1].[Sum] > 10 THEN CAST(1 AS bit) +) AS [s1] ON [l].[Id] = [s1].[Key] AND [s1].[Sum] > 10 +"""); + } + + public override async Task Composite_key_join_on_groupby_aggregate_projecting_only_grouping_key2(bool async) + { + await base.Composite_key_join_on_groupby_aggregate_projecting_only_grouping_key2(async); + + AssertSql( + """ +SELECT [s1].[Key] +FROM [Level1] AS [l] +INNER JOIN ( + SELECT [s].[Key], ( + SELECT COALESCE(SUM(CASE + WHEN [l7].[OneToOne_Required_PK_Date] IS NOT NULL AND [l7].[Level1_Required_Id] IS NOT NULL AND [l7].[OneToMany_Required_Inverse2Id] IS NOT NULL THEN [l7].[Id] + END), 0) + FROM ( + SELECT [l3].[Id], CASE + WHEN [l4].[OneToOne_Required_PK_Date] IS NOT NULL AND [l4].[Level1_Required_Id] IS NOT NULL AND [l4].[OneToMany_Required_Inverse2Id] IS NOT NULL THEN [l4].[Id] + END % 3 AS [Key] + FROM [Level1] AS [l3] + LEFT JOIN ( + SELECT [l5].[Id], [l5].[OneToOne_Required_PK_Date], [l5].[Level1_Required_Id], [l5].[OneToMany_Required_Inverse2Id] + FROM [Level1] AS [l5] + WHERE [l5].[OneToOne_Required_PK_Date] IS NOT NULL AND [l5].[Level1_Required_Id] IS NOT NULL AND [l5].[OneToMany_Required_Inverse2Id] IS NOT NULL + ) AS [l4] ON [l3].[Id] = CASE + WHEN [l4].[OneToOne_Required_PK_Date] IS NOT NULL AND [l4].[Level1_Required_Id] IS NOT NULL AND [l4].[OneToMany_Required_Inverse2Id] IS NOT NULL THEN [l4].[Id] + END + WHERE [l4].[OneToOne_Required_PK_Date] IS NOT NULL AND [l4].[Level1_Required_Id] IS NOT NULL AND [l4].[OneToMany_Required_Inverse2Id] IS NOT NULL + ) AS [s0] + LEFT JOIN ( + SELECT [l6].[Id], [l6].[OneToOne_Required_PK_Date], [l6].[Level1_Required_Id], [l6].[OneToMany_Required_Inverse2Id] + FROM [Level1] AS [l6] + WHERE [l6].[OneToOne_Required_PK_Date] IS NOT NULL AND [l6].[Level1_Required_Id] IS NOT NULL AND [l6].[OneToMany_Required_Inverse2Id] IS NOT NULL + ) AS [l7] ON [s0].[Id] = CASE + WHEN [l7].[OneToOne_Required_PK_Date] IS NOT NULL AND [l7].[Level1_Required_Id] IS NOT NULL AND [l7].[OneToMany_Required_Inverse2Id] IS NOT NULL THEN [l7].[Id] + END + WHERE [s].[Key] = [s0].[Key] OR ([s].[Key] IS NULL AND [s0].[Key] IS NULL)) AS [Sum] + FROM ( + SELECT CASE + WHEN [l2].[OneToOne_Required_PK_Date] IS NOT NULL AND [l2].[Level1_Required_Id] IS NOT NULL AND [l2].[OneToMany_Required_Inverse2Id] IS NOT NULL THEN [l2].[Id] + END % 3 AS [Key] + FROM [Level1] AS [l0] + LEFT JOIN ( + SELECT [l1].[Id], [l1].[OneToOne_Required_PK_Date], [l1].[Level1_Required_Id], [l1].[OneToMany_Required_Inverse2Id] + FROM [Level1] AS [l1] + WHERE [l1].[OneToOne_Required_PK_Date] IS NOT NULL AND [l1].[Level1_Required_Id] IS NOT NULL AND [l1].[OneToMany_Required_Inverse2Id] IS NOT NULL + ) AS [l2] ON [l0].[Id] = CASE + WHEN [l2].[OneToOne_Required_PK_Date] IS NOT NULL AND [l2].[Level1_Required_Id] IS NOT NULL AND [l2].[OneToMany_Required_Inverse2Id] IS NOT NULL THEN [l2].[Id] + END + WHERE [l2].[OneToOne_Required_PK_Date] IS NOT NULL AND [l2].[Level1_Required_Id] IS NOT NULL AND [l2].[OneToMany_Required_Inverse2Id] IS NOT NULL + ) AS [s] + GROUP BY [s].[Key] +) AS [s1] ON [l].[Id] = [s1].[Key] AND CAST(0 AS bit) = CASE + WHEN [s1].[Sum] <= 10 THEN CAST(1 AS bit) ELSE CAST(0 AS bit) END """); diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/GearsOfWarQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/GearsOfWarQuerySqlServerTest.cs index 11677a5fade..6198c915ad7 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/GearsOfWarQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/GearsOfWarQuerySqlServerTest.cs @@ -945,7 +945,7 @@ public override async Task Null_propagation_optimization1(bool async) """ SELECT [g].[Nickname], [g].[SquadId], [g].[AssignedCityName], [g].[CityOfBirthName], [g].[Discriminator], [g].[FullName], [g].[HasSoulPatch], [g].[LeaderNickname], [g].[LeaderSquadId], [g].[Rank] FROM [Gears] AS [g] -WHERE [g].[LeaderNickname] = N'Marcus' AND [g].[LeaderNickname] IS NOT NULL +WHERE [g].[LeaderNickname] = N'Marcus' """); } @@ -999,10 +999,7 @@ FROM [Gears] AS [g] WHERE CASE WHEN [g].[LeaderNickname] IS NULL THEN NULL ELSE CAST(LEN([g].[LeaderNickname]) AS int) -END = 5 AND CASE - WHEN [g].[LeaderNickname] IS NULL THEN NULL - ELSE CAST(LEN([g].[LeaderNickname]) AS int) -END IS NOT NULL +END = 5 """); } @@ -1018,10 +1015,7 @@ FROM [Gears] AS [g] WHERE CASE WHEN [g].[LeaderNickname] IS NOT NULL THEN CAST(LEN([g].[LeaderNickname]) AS int) ELSE NULL -END = 5 AND CASE - WHEN [g].[LeaderNickname] IS NOT NULL THEN CAST(LEN([g].[LeaderNickname]) AS int) - ELSE NULL -END IS NOT NULL +END = 5 """); } @@ -1037,10 +1031,7 @@ FROM [Gears] AS [g] WHERE CASE WHEN [g].[LeaderNickname] IS NOT NULL THEN CAST(LEN([g].[LeaderNickname]) AS int) ELSE NULL -END = 5 AND CASE - WHEN [g].[LeaderNickname] IS NOT NULL THEN CAST(LEN([g].[LeaderNickname]) AS int) - ELSE NULL -END IS NOT NULL +END = 5 """); } diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/NullSemanticsQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/NullSemanticsQuerySqlServerTest.cs index cecc6e20f4d..524521ac452 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/NullSemanticsQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/NullSemanticsQuerySqlServerTest.cs @@ -906,6 +906,18 @@ FROM [Entities1] AS [e] """); } + public override async Task Join_uses_csharp_semantics_for_anon_objects(bool async) + { + await base.Join_uses_csharp_semantics_for_anon_objects(async); + + AssertSql( + """ +SELECT [e].[Id] AS [Id1], [e0].[Id] AS [Id2], [e].[NullableIntA], [e0].[NullableIntB] +FROM [Entities1] AS [e] +INNER JOIN [Entities2] AS [e0] ON [e].[NullableIntA] = [e0].[NullableIntB] OR ([e].[NullableIntA] IS NULL AND [e0].[NullableIntB] IS NULL) +"""); + } + public override async Task Contains_with_local_array_closure_with_null(bool async) { await base.Contains_with_local_array_closure_with_null(async); diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/TPCGearsOfWarQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/TPCGearsOfWarQuerySqlServerTest.cs index 737819a1b91..fa76dc4a5c6 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/TPCGearsOfWarQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/TPCGearsOfWarQuerySqlServerTest.cs @@ -1319,7 +1319,7 @@ UNION ALL SELECT [o].[Nickname], [o].[SquadId], [o].[AssignedCityName], [o].[CityOfBirthName], [o].[FullName], [o].[HasSoulPatch], [o].[LeaderNickname], [o].[LeaderSquadId], [o].[Rank], N'Officer' AS [Discriminator] FROM [Officers] AS [o] ) AS [u] -WHERE [u].[LeaderNickname] = N'Marcus' AND [u].[LeaderNickname] IS NOT NULL +WHERE [u].[LeaderNickname] = N'Marcus' """); } @@ -1391,10 +1391,7 @@ FROM [Officers] AS [o] WHERE CASE WHEN [u].[LeaderNickname] IS NULL THEN NULL ELSE CAST(LEN([u].[LeaderNickname]) AS int) -END = 5 AND CASE - WHEN [u].[LeaderNickname] IS NULL THEN NULL - ELSE CAST(LEN([u].[LeaderNickname]) AS int) -END IS NOT NULL +END = 5 """); } @@ -1416,10 +1413,7 @@ FROM [Officers] AS [o] WHERE CASE WHEN [u].[LeaderNickname] IS NOT NULL THEN CAST(LEN([u].[LeaderNickname]) AS int) ELSE NULL -END = 5 AND CASE - WHEN [u].[LeaderNickname] IS NOT NULL THEN CAST(LEN([u].[LeaderNickname]) AS int) - ELSE NULL -END IS NOT NULL +END = 5 """); } @@ -1441,10 +1435,7 @@ FROM [Officers] AS [o] WHERE CASE WHEN [u].[LeaderNickname] IS NOT NULL THEN CAST(LEN([u].[LeaderNickname]) AS int) ELSE NULL -END = 5 AND CASE - WHEN [u].[LeaderNickname] IS NOT NULL THEN CAST(LEN([u].[LeaderNickname]) AS int) - ELSE NULL -END IS NOT NULL +END = 5 """); } diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/TPTGearsOfWarQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/TPTGearsOfWarQuerySqlServerTest.cs index 33b337791d1..6df09080b56 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/TPTGearsOfWarQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/TPTGearsOfWarQuerySqlServerTest.cs @@ -1148,7 +1148,7 @@ WHEN [o].[Nickname] IS NOT NULL THEN N'Officer' END AS [Discriminator] FROM [Gears] AS [g] LEFT JOIN [Officers] AS [o] ON [g].[Nickname] = [o].[Nickname] AND [g].[SquadId] = [o].[SquadId] -WHERE [g].[LeaderNickname] = N'Marcus' AND [g].[LeaderNickname] IS NOT NULL +WHERE [g].[LeaderNickname] = N'Marcus' """); } @@ -1211,10 +1211,7 @@ FROM [Gears] AS [g] WHERE CASE WHEN [g].[LeaderNickname] IS NULL THEN NULL ELSE CAST(LEN([g].[LeaderNickname]) AS int) -END = 5 AND CASE - WHEN [g].[LeaderNickname] IS NULL THEN NULL - ELSE CAST(LEN([g].[LeaderNickname]) AS int) -END IS NOT NULL +END = 5 """); } @@ -1233,10 +1230,7 @@ FROM [Gears] AS [g] WHERE CASE WHEN [g].[LeaderNickname] IS NOT NULL THEN CAST(LEN([g].[LeaderNickname]) AS int) ELSE NULL -END = 5 AND CASE - WHEN [g].[LeaderNickname] IS NOT NULL THEN CAST(LEN([g].[LeaderNickname]) AS int) - ELSE NULL -END IS NOT NULL +END = 5 """); } @@ -1255,10 +1249,7 @@ FROM [Gears] AS [g] WHERE CASE WHEN [g].[LeaderNickname] IS NOT NULL THEN CAST(LEN([g].[LeaderNickname]) AS int) ELSE NULL -END = 5 AND CASE - WHEN [g].[LeaderNickname] IS NOT NULL THEN CAST(LEN([g].[LeaderNickname]) AS int) - ELSE NULL -END IS NOT NULL +END = 5 """); } diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/TemporalGearsOfWarQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/TemporalGearsOfWarQuerySqlServerTest.cs index ac6fedba78c..4804b9024ed 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/TemporalGearsOfWarQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/TemporalGearsOfWarQuerySqlServerTest.cs @@ -1187,7 +1187,7 @@ public override async Task Null_propagation_optimization1(bool async) """ SELECT [g].[Nickname], [g].[SquadId], [g].[AssignedCityName], [g].[CityOfBirthName], [g].[Discriminator], [g].[FullName], [g].[HasSoulPatch], [g].[LeaderNickname], [g].[LeaderSquadId], [g].[PeriodEnd], [g].[PeriodStart], [g].[Rank] FROM [Gears] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [g] -WHERE [g].[LeaderNickname] = N'Marcus' AND [g].[LeaderNickname] IS NOT NULL +WHERE [g].[LeaderNickname] = N'Marcus' """); } @@ -1605,10 +1605,7 @@ public override async Task Null_propagation_optimization6(bool async) WHERE CASE WHEN [g].[LeaderNickname] IS NOT NULL THEN CAST(LEN([g].[LeaderNickname]) AS int) ELSE NULL -END = 5 AND CASE - WHEN [g].[LeaderNickname] IS NOT NULL THEN CAST(LEN([g].[LeaderNickname]) AS int) - ELSE NULL -END IS NOT NULL +END = 5 """); } @@ -4039,10 +4036,7 @@ public override async Task Null_propagation_optimization5(bool async) WHERE CASE WHEN [g].[LeaderNickname] IS NOT NULL THEN CAST(LEN([g].[LeaderNickname]) AS int) ELSE NULL -END = 5 AND CASE - WHEN [g].[LeaderNickname] IS NOT NULL THEN CAST(LEN([g].[LeaderNickname]) AS int) - ELSE NULL -END IS NOT NULL +END = 5 """); } @@ -4971,10 +4965,7 @@ public override async Task Null_propagation_optimization4(bool async) WHERE CASE WHEN [g].[LeaderNickname] IS NULL THEN NULL ELSE CAST(LEN([g].[LeaderNickname]) AS int) -END = 5 AND CASE - WHEN [g].[LeaderNickname] IS NULL THEN NULL - ELSE CAST(LEN([g].[LeaderNickname]) AS int) -END IS NOT NULL +END = 5 """); } diff --git a/test/EFCore.Sqlite.FunctionalTests/Query/GearsOfWarQuerySqliteTest.cs b/test/EFCore.Sqlite.FunctionalTests/Query/GearsOfWarQuerySqliteTest.cs index 658cd0f4bce..0ff7f0f8cf9 100644 --- a/test/EFCore.Sqlite.FunctionalTests/Query/GearsOfWarQuerySqliteTest.cs +++ b/test/EFCore.Sqlite.FunctionalTests/Query/GearsOfWarQuerySqliteTest.cs @@ -1066,10 +1066,7 @@ public override async Task Null_propagation_optimization6(bool async) WHERE CASE WHEN "g"."LeaderNickname" IS NOT NULL THEN length("g"."LeaderNickname") ELSE NULL -END = 5 AND CASE - WHEN "g"."LeaderNickname" IS NOT NULL THEN length("g"."LeaderNickname") - ELSE NULL -END IS NOT NULL +END = 5 """); } @@ -1566,7 +1563,7 @@ public override async Task Null_propagation_optimization3(bool async) WHERE CASE WHEN "g"."LeaderNickname" IS NOT NULL THEN "g"."LeaderNickname" LIKE '%us' ELSE NULL -END = 1 +END """); } @@ -3370,7 +3367,7 @@ public override async Task Null_propagation_optimization2(bool async) WHERE CASE WHEN "g"."LeaderNickname" IS NULL THEN NULL ELSE "g"."LeaderNickname" LIKE '%us' AND "g"."LeaderNickname" IS NOT NULL -END = 1 +END """); } @@ -5996,7 +5993,7 @@ public override async Task ToString_boolean_property_nullable(bool async) """ SELECT CASE WHEN "f"."Eradicated" = 0 THEN 'False' - WHEN "f"."Eradicated" = 1 THEN 'True' + WHEN "f"."Eradicated" THEN 'True' ELSE NULL END FROM "Factions" AS "f" @@ -7486,10 +7483,7 @@ public override async Task Null_propagation_optimization5(bool async) WHERE CASE WHEN "g"."LeaderNickname" IS NOT NULL THEN length("g"."LeaderNickname") ELSE NULL -END = 5 AND CASE - WHEN "g"."LeaderNickname" IS NOT NULL THEN length("g"."LeaderNickname") - ELSE NULL -END IS NOT NULL +END = 5 """); } @@ -8275,10 +8269,7 @@ public override async Task Null_propagation_optimization4(bool async) WHERE CASE WHEN "g"."LeaderNickname" IS NULL THEN NULL ELSE length("g"."LeaderNickname") -END = 5 AND CASE - WHEN "g"."LeaderNickname" IS NULL THEN NULL - ELSE length("g"."LeaderNickname") -END IS NOT NULL +END = 5 """); } @@ -8481,7 +8472,7 @@ public override async Task Null_propagation_optimization1(bool async) """ SELECT "g"."Nickname", "g"."SquadId", "g"."AssignedCityName", "g"."CityOfBirthName", "g"."Discriminator", "g"."FullName", "g"."HasSoulPatch", "g"."LeaderNickname", "g"."LeaderSquadId", "g"."Rank" FROM "Gears" AS "g" -WHERE "g"."LeaderNickname" = 'Marcus' AND "g"."LeaderNickname" IS NOT NULL +WHERE "g"."LeaderNickname" = 'Marcus' """); } diff --git a/test/EFCore.Sqlite.FunctionalTests/Query/NullSemanticsQuerySqliteTest.cs b/test/EFCore.Sqlite.FunctionalTests/Query/NullSemanticsQuerySqliteTest.cs index 12b90479a50..3ef14c2ba98 100644 --- a/test/EFCore.Sqlite.FunctionalTests/Query/NullSemanticsQuerySqliteTest.cs +++ b/test/EFCore.Sqlite.FunctionalTests/Query/NullSemanticsQuerySqliteTest.cs @@ -17,6 +17,30 @@ public NullSemanticsQuerySqliteTest(NullSemanticsQuerySqliteFixture fixture, ITe //Fixture.TestSqlLoggerFactory.SetTestOutputHelper(testOutputHelper); } + public override async Task Join_uses_database_semantics(bool async) + { + await base.Join_uses_database_semantics(async); + + AssertSql( + """ +SELECT "e"."Id" AS "Id1", "e0"."Id" AS "Id2", "e"."NullableIntA", "e0"."NullableIntB" +FROM "Entities1" AS "e" +INNER JOIN "Entities2" AS "e0" ON "e"."NullableIntA" = "e0"."NullableIntB" +"""); + } + + public override async Task Join_uses_csharp_semantics_for_anon_objects(bool async) + { + await base.Join_uses_csharp_semantics_for_anon_objects(async); + + AssertSql( + """ +SELECT "e"."Id" AS "Id1", "e0"."Id" AS "Id2", "e"."NullableIntA", "e0"."NullableIntB" +FROM "Entities1" AS "e" +INNER JOIN "Entities2" AS "e0" ON "e"."NullableIntA" = "e0"."NullableIntB" OR ("e"."NullableIntA" IS NULL AND "e0"."NullableIntB" IS NULL) +"""); + } + public override async Task Null_semantics_contains_non_nullable_item_with_non_nullable_subquery(bool async) { await base.Null_semantics_contains_non_nullable_item_with_non_nullable_subquery(async);