From 5b6c3f7a05243a12000793b8abd3b3c7b2771971 Mon Sep 17 00:00:00 2001 From: Andrea Canciani Date: Sat, 1 Jun 2024 14:09:18 +0200 Subject: [PATCH 1/6] Add tests for `CASE op WHEN` --- .../Query/NullSemanticsQueryFixtureBase.cs | 17 +++++++++ .../Query/NullSemanticsQueryTestBase.cs | 25 +++++++++++++ .../Query/NullSemanticsQuerySqlServerTest.cs | 36 +++++++++++++++++++ .../Query/NullSemanticsQuerySqliteTest.cs | 30 ++++++++++++++++ 4 files changed, 108 insertions(+) diff --git a/test/EFCore.Relational.Specification.Tests/Query/NullSemanticsQueryFixtureBase.cs b/test/EFCore.Relational.Specification.Tests/Query/NullSemanticsQueryFixtureBase.cs index 57589808a11..8cc44b96471 100644 --- a/test/EFCore.Relational.Specification.Tests/Query/NullSemanticsQueryFixtureBase.cs +++ b/test/EFCore.Relational.Specification.Tests/Query/NullSemanticsQueryFixtureBase.cs @@ -129,6 +129,16 @@ protected override void OnModelCreating(ModelBuilder modelBuilder, DbContext con new CaseWhenClause(args[4], args[5]), ])) ); + + modelBuilder.HasDbFunction( + typeof(NullSemanticsQueryFixtureBase).GetMethod(nameof(BoolSwitch)), + b => b.HasTranslation(args => new CaseExpression( + operand: args[0], + [ + new CaseWhenClause(new SqlConstantExpression(true, typeMapping: BoolTypeMapping.Default), args[1]), + new CaseWhenClause(new SqlConstantExpression(false, typeMapping: BoolTypeMapping.Default), args[2]), + ])) + ); } public static int? Cases(bool c1, int v1, bool c2, int v2, bool c3, int v3) => @@ -136,4 +146,11 @@ protected override void OnModelCreating(ModelBuilder modelBuilder, DbContext con c2 ? v2 : c3 ? v3 : null; + + public static int BoolSwitch(bool x, int whenTrue, int whenFalse) => + x switch + { + true => whenTrue, + false => whenFalse, + }; } diff --git a/test/EFCore.Relational.Specification.Tests/Query/NullSemanticsQueryTestBase.cs b/test/EFCore.Relational.Specification.Tests/Query/NullSemanticsQueryTestBase.cs index f2101c0b6ec..42c1fe02d0c 100644 --- a/test/EFCore.Relational.Specification.Tests/Query/NullSemanticsQueryTestBase.cs +++ b/test/EFCore.Relational.Specification.Tests/Query/NullSemanticsQueryTestBase.cs @@ -1796,6 +1796,31 @@ public virtual Task CaseWhen_equal_to_first_or_third_select(bool async) assertOrder: true ); + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task CaseOpWhen_projection(bool async) + => AssertQuery( + async, + ss => ss.Set() + .OrderBy(x => x.Id) + .Select(x => NullSemanticsQueryFixtureBase.BoolSwitch( + x.StringA == "Foo", 3, 2 + )), + assertOrder: true + ); + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task CaseOpWhen_predicate(bool async) + => AssertQuery( + async, + ss => ss.Set() + .Where(x => NullSemanticsQueryFixtureBase.BoolSwitch( + x.StringA == "Foo", 3, 2 + ) == 2), + assertOrder: true + ); + [ConditionalTheory] [MemberData(nameof(IsAsyncData))] public virtual Task False_compared_to_negated_is_null(bool async) diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/NullSemanticsQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/NullSemanticsQuerySqlServerTest.cs index 24575897ce7..3d908c6b4c6 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/NullSemanticsQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/NullSemanticsQuerySqlServerTest.cs @@ -2860,6 +2860,42 @@ ORDER BY [e].[Id] """); } + public override async Task CaseOpWhen_projection(bool async) + { + await base.CaseOpWhen_projection(async); + + AssertSql( + """ +SELECT CASE CASE + WHEN [e].[StringA] = N'Foo' THEN CAST(1 AS bit) + ELSE CAST(0 AS bit) +END + WHEN 1 THEN 3 + WHEN 0 THEN 2 +END +FROM [Entities1] AS [e] +ORDER BY [e].[Id] +"""); + } + + public override async Task CaseOpWhen_predicate(bool async) + { + await base.CaseOpWhen_predicate(async); + + AssertSql( + """ +SELECT [e].[Id], [e].[BoolA], [e].[BoolB], [e].[BoolC], [e].[IntA], [e].[IntB], [e].[IntC], [e].[NullableBoolA], [e].[NullableBoolB], [e].[NullableBoolC], [e].[NullableIntA], [e].[NullableIntB], [e].[NullableIntC], [e].[NullableStringA], [e].[NullableStringB], [e].[NullableStringC], [e].[StringA], [e].[StringB], [e].[StringC] +FROM [Entities1] AS [e] +WHERE CASE CASE + WHEN [e].[StringA] = N'Foo' THEN CAST(1 AS bit) + ELSE CAST(0 AS bit) +END + WHEN 1 THEN 3 + WHEN 0 THEN 2 +END = 2 +"""); + } + public override async Task Multiple_non_equality_comparisons_with_null_in_the_middle(bool async) { await base.Multiple_non_equality_comparisons_with_null_in_the_middle(async); diff --git a/test/EFCore.Sqlite.FunctionalTests/Query/NullSemanticsQuerySqliteTest.cs b/test/EFCore.Sqlite.FunctionalTests/Query/NullSemanticsQuerySqliteTest.cs index 6036dbdc033..a73ea1d261e 100644 --- a/test/EFCore.Sqlite.FunctionalTests/Query/NullSemanticsQuerySqliteTest.cs +++ b/test/EFCore.Sqlite.FunctionalTests/Query/NullSemanticsQuerySqliteTest.cs @@ -256,6 +256,36 @@ ORDER BY "e"."Id" """); } + public override async Task CaseOpWhen_projection(bool async) + { + await base.CaseOpWhen_projection(async); + + AssertSql( + """ +SELECT CASE "e"."StringA" = 'Foo' + WHEN 1 THEN 3 + WHEN 0 THEN 2 +END +FROM "Entities1" AS "e" +ORDER BY "e"."Id" +"""); + } + + public override async Task CaseOpWhen_predicate(bool async) + { + await base.CaseOpWhen_predicate(async); + + AssertSql( + """ +SELECT "e"."Id", "e"."BoolA", "e"."BoolB", "e"."BoolC", "e"."IntA", "e"."IntB", "e"."IntC", "e"."NullableBoolA", "e"."NullableBoolB", "e"."NullableBoolC", "e"."NullableIntA", "e"."NullableIntB", "e"."NullableIntC", "e"."NullableStringA", "e"."NullableStringB", "e"."NullableStringC", "e"."StringA", "e"."StringB", "e"."StringC" +FROM "Entities1" AS "e" +WHERE CASE "e"."StringA" = 'Foo' + WHEN 1 THEN 3 + WHEN 0 THEN 2 +END = 2 +"""); + } + public override async Task Bool_equal_nullable_bool_HasValue(bool async) { await base.Bool_equal_nullable_bool_HasValue(async); From 59e1b50bf441944a15d16a2ce2916ea6e6098f60 Mon Sep 17 00:00:00 2001 From: Andrea Canciani Date: Sat, 1 Jun 2024 13:09:39 +0200 Subject: [PATCH 2/6] Fix optimization of `CASE op WHEN` --- .../Query/SqlNullabilityProcessor.cs | 22 ++++++++++++------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/src/EFCore.Relational/Query/SqlNullabilityProcessor.cs b/src/EFCore.Relational/Query/SqlNullabilityProcessor.cs index c371c7622ba..3bba37c6f2e 100644 --- a/src/EFCore.Relational/Query/SqlNullabilityProcessor.cs +++ b/src/EFCore.Relational/Query/SqlNullabilityProcessor.cs @@ -515,11 +515,16 @@ protected virtual SqlExpression VisitCase(CaseExpression caseExpression, bool al var test = Visit( whenClause.Test, allowOptimizedExpansion: testIsCondition, preserveColumnNullabilityInformation: true, out _); - if (IsTrue(test)) + var testCondition = testIsCondition + ? test + : Visit(_sqlExpressionFactory.Equal(operand!, test), + allowOptimizedExpansion: testIsCondition, preserveColumnNullabilityInformation: true, out _); + + if (IsTrue(testCondition)) { testEvaluatesToTrue = true; } - else if (IsFalse(test)) + else if (IsFalse(testCondition)) { // if test evaluates to 'false' we can remove the WhenClause RestoreNonNullableColumnsList(currentNonNullableColumnsCount); @@ -538,6 +543,12 @@ protected virtual SqlExpression VisitCase(CaseExpression caseExpression, bool al // if test evaluates to 'true' we can remove every condition that comes after, including ElseResult if (testEvaluatesToTrue) { + // if the first When clause is always satisfied, simply return its result + if (whenClauses.Count == 1) + { + return whenClauses[0].Result; + } + break; } } @@ -560,12 +571,7 @@ protected virtual SqlExpression VisitCase(CaseExpression caseExpression, bool al return elseResult ?? _sqlExpressionFactory.Constant(null, caseExpression.Type, caseExpression.TypeMapping); } - // if there is only one When clause and it's test evaluates to 'true' AND there is no else block, simply return the result - return elseResult == null - && whenClauses.Count == 1 - && IsTrue(whenClauses[0].Test) - ? whenClauses[0].Result - : caseExpression.Update(operand, whenClauses, elseResult); + return caseExpression.Update(operand, whenClauses, elseResult); } /// From f2bcee5af235bfa8b607acdf8f91552b930481c6 Mon Sep 17 00:00:00 2001 From: Andrea Canciani Date: Sat, 1 Jun 2024 15:29:15 +0200 Subject: [PATCH 3/6] Improve bool -> string translation The new translation avoids duplicating sub-expressions and introducing negations. --- .../Translators/SqlServerObjectToStringTranslator.cs | 11 ++++++----- .../Translators/SqliteObjectToStringTranslator.cs | 11 ++++++----- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/src/EFCore.SqlServer/Query/Internal/Translators/SqlServerObjectToStringTranslator.cs b/src/EFCore.SqlServer/Query/Internal/Translators/SqlServerObjectToStringTranslator.cs index 363079ca618..b55776e03ba 100644 --- a/src/EFCore.SqlServer/Query/Internal/Translators/SqlServerObjectToStringTranslator.cs +++ b/src/EFCore.SqlServer/Query/Internal/Translators/SqlServerObjectToStringTranslator.cs @@ -82,13 +82,14 @@ public SqlServerObjectToStringTranslator(ISqlExpressionFactory sqlExpressionFact if (instance is ColumnExpression { IsNullable: true }) { return _sqlExpressionFactory.Case( + instance, new[] { new CaseWhenClause( - _sqlExpressionFactory.Equal(instance, _sqlExpressionFactory.Constant(false)), + _sqlExpressionFactory.Constant(false), _sqlExpressionFactory.Constant(false.ToString())), new CaseWhenClause( - _sqlExpressionFactory.Equal(instance, _sqlExpressionFactory.Constant(true)), + _sqlExpressionFactory.Constant(true), _sqlExpressionFactory.Constant(true.ToString())) }, _sqlExpressionFactory.Constant(null, typeof(string))); @@ -98,10 +99,10 @@ public SqlServerObjectToStringTranslator(ISqlExpressionFactory sqlExpressionFact new[] { new CaseWhenClause( - _sqlExpressionFactory.Equal(instance, _sqlExpressionFactory.Constant(false)), - _sqlExpressionFactory.Constant(false.ToString())) + instance, + _sqlExpressionFactory.Constant(true.ToString())) }, - _sqlExpressionFactory.Constant(true.ToString())); + _sqlExpressionFactory.Constant(false.ToString())); } return TypeMapping.TryGetValue(instance.Type, out var storeType) diff --git a/src/EFCore.Sqlite.Core/Query/Internal/Translators/SqliteObjectToStringTranslator.cs b/src/EFCore.Sqlite.Core/Query/Internal/Translators/SqliteObjectToStringTranslator.cs index a8753c192b4..4c89a8c3e83 100644 --- a/src/EFCore.Sqlite.Core/Query/Internal/Translators/SqliteObjectToStringTranslator.cs +++ b/src/EFCore.Sqlite.Core/Query/Internal/Translators/SqliteObjectToStringTranslator.cs @@ -77,13 +77,14 @@ public SqliteObjectToStringTranslator(ISqlExpressionFactory sqlExpressionFactory if (instance is ColumnExpression { IsNullable: true }) { return _sqlExpressionFactory.Case( + instance, new[] { new CaseWhenClause( - _sqlExpressionFactory.Equal(instance, _sqlExpressionFactory.Constant(false)), + _sqlExpressionFactory.Constant(false), _sqlExpressionFactory.Constant(false.ToString())), new CaseWhenClause( - _sqlExpressionFactory.Equal(instance, _sqlExpressionFactory.Constant(true)), + _sqlExpressionFactory.Constant(true), _sqlExpressionFactory.Constant(true.ToString())) }, _sqlExpressionFactory.Constant(null, typeof(string))); @@ -93,10 +94,10 @@ public SqliteObjectToStringTranslator(ISqlExpressionFactory sqlExpressionFactory new[] { new CaseWhenClause( - _sqlExpressionFactory.Equal(instance, _sqlExpressionFactory.Constant(false)), - _sqlExpressionFactory.Constant(false.ToString())) + instance, + _sqlExpressionFactory.Constant(true.ToString())) }, - _sqlExpressionFactory.Constant(true.ToString())); + _sqlExpressionFactory.Constant(false.ToString())); } return TypeMapping.Contains(instance.Type) From 0064d0c3c31d4e344bb81c933ad6302bee960a18 Mon Sep 17 00:00:00 2001 From: Andrea Canciani Date: Sat, 1 Jun 2024 15:29:30 +0200 Subject: [PATCH 4/6] Update tests --- .../Query/GearsOfWarQuerySqlServerTest.cs | 10 +++++----- .../Query/TPCGearsOfWarQuerySqlServerTest.cs | 10 +++++----- .../Query/TPTGearsOfWarQuerySqlServerTest.cs | 10 +++++----- .../Query/TemporalGearsOfWarQuerySqlServerTest.cs | 10 +++++----- .../Query/GearsOfWarQuerySqliteTest.cs | 10 +++++----- 5 files changed, 25 insertions(+), 25 deletions(-) diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/GearsOfWarQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/GearsOfWarQuerySqlServerTest.cs index 2cc24ac5c8c..84ed9fc7e9a 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/GearsOfWarQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/GearsOfWarQuerySqlServerTest.cs @@ -4007,8 +4007,8 @@ public override async Task ToString_boolean_property_non_nullable(bool async) AssertSql( """ SELECT CASE - WHEN [w].[IsAutomatic] = CAST(0 AS bit) THEN N'False' - ELSE N'True' + WHEN [w].[IsAutomatic] = CAST(1 AS bit) THEN N'True' + ELSE N'False' END FROM [Weapons] AS [w] """); @@ -4020,9 +4020,9 @@ public override async Task ToString_boolean_property_nullable(bool async) AssertSql( """ -SELECT CASE - WHEN [f].[Eradicated] = CAST(0 AS bit) THEN N'False' - WHEN [f].[Eradicated] = CAST(1 AS bit) THEN N'True' +SELECT CASE [f].[Eradicated] + WHEN CAST(0 AS bit) THEN N'False' + WHEN CAST(1 AS bit) THEN N'True' ELSE NULL END FROM [Factions] AS [f] diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/TPCGearsOfWarQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/TPCGearsOfWarQuerySqlServerTest.cs index e8e339ef27f..b0f572c558d 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/TPCGearsOfWarQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/TPCGearsOfWarQuerySqlServerTest.cs @@ -12632,9 +12632,9 @@ public override async Task ToString_boolean_property_nullable(bool async) AssertSql( """ -SELECT CASE - WHEN [l].[Eradicated] = CAST(0 AS bit) THEN N'False' - WHEN [l].[Eradicated] = CAST(1 AS bit) THEN N'True' +SELECT CASE [l].[Eradicated] + WHEN CAST(0 AS bit) THEN N'False' + WHEN CAST(1 AS bit) THEN N'True' ELSE NULL END FROM [LocustHordes] AS [l] @@ -12693,8 +12693,8 @@ public override async Task ToString_boolean_property_non_nullable(bool async) AssertSql( """ SELECT CASE - WHEN [w].[IsAutomatic] = CAST(0 AS bit) THEN N'False' - ELSE N'True' + WHEN [w].[IsAutomatic] = CAST(1 AS bit) THEN N'True' + ELSE N'False' END FROM [Weapons] AS [w] """); diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/TPTGearsOfWarQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/TPTGearsOfWarQuerySqlServerTest.cs index 01391120231..bda25bbdf74 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/TPTGearsOfWarQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/TPTGearsOfWarQuerySqlServerTest.cs @@ -10797,9 +10797,9 @@ public override async Task ToString_boolean_property_nullable(bool async) AssertSql( """ -SELECT CASE - WHEN [l].[Eradicated] = CAST(0 AS bit) THEN N'False' - WHEN [l].[Eradicated] = CAST(1 AS bit) THEN N'True' +SELECT CASE [l].[Eradicated] + WHEN CAST(0 AS bit) THEN N'False' + WHEN CAST(1 AS bit) THEN N'True' ELSE NULL END FROM [Factions] AS [f] @@ -10853,8 +10853,8 @@ public override async Task ToString_boolean_property_non_nullable(bool async) AssertSql( """ SELECT CASE - WHEN [w].[IsAutomatic] = CAST(0 AS bit) THEN N'False' - ELSE N'True' + WHEN [w].[IsAutomatic] = CAST(1 AS bit) THEN N'True' + ELSE N'False' END FROM [Weapons] AS [w] """); diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/TemporalGearsOfWarQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/TemporalGearsOfWarQuerySqlServerTest.cs index 5e9ba7444b6..23d302a1a6b 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/TemporalGearsOfWarQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/TemporalGearsOfWarQuerySqlServerTest.cs @@ -8259,9 +8259,9 @@ public override async Task ToString_boolean_property_nullable(bool async) AssertSql( """ -SELECT CASE - WHEN [f].[Eradicated] = CAST(0 AS bit) THEN N'False' - WHEN [f].[Eradicated] = CAST(1 AS bit) THEN N'True' +SELECT CASE [f].[Eradicated] + WHEN CAST(0 AS bit) THEN N'False' + WHEN CAST(1 AS bit) THEN N'True' ELSE NULL END FROM [Factions] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [f] @@ -9192,8 +9192,8 @@ public override async Task ToString_boolean_property_non_nullable(bool async) AssertSql( """ SELECT CASE - WHEN [w].[IsAutomatic] = CAST(0 AS bit) THEN N'False' - ELSE N'True' + WHEN [w].[IsAutomatic] = CAST(1 AS bit) THEN N'True' + ELSE N'False' END FROM [Weapons] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [w] """); diff --git a/test/EFCore.Sqlite.FunctionalTests/Query/GearsOfWarQuerySqliteTest.cs b/test/EFCore.Sqlite.FunctionalTests/Query/GearsOfWarQuerySqliteTest.cs index 87817d139b6..805aec16a04 100644 --- a/test/EFCore.Sqlite.FunctionalTests/Query/GearsOfWarQuerySqliteTest.cs +++ b/test/EFCore.Sqlite.FunctionalTests/Query/GearsOfWarQuerySqliteTest.cs @@ -3187,8 +3187,8 @@ public override async Task ToString_boolean_property_non_nullable(bool async) AssertSql( """ SELECT CASE - WHEN NOT ("w"."IsAutomatic") THEN 'False' - ELSE 'True' + WHEN "w"."IsAutomatic" THEN 'True' + ELSE 'False' END FROM "Weapons" AS "w" """); @@ -5994,9 +5994,9 @@ public override async Task ToString_boolean_property_nullable(bool async) AssertSql( """ -SELECT CASE - WHEN "f"."Eradicated" = 0 THEN 'False' - WHEN "f"."Eradicated" THEN 'True' +SELECT CASE "f"."Eradicated" + WHEN 0 THEN 'False' + WHEN 1 THEN 'True' ELSE NULL END FROM "Factions" AS "f" From 9785ef58377eb0aa3d4f92ce3c40e0c8352091e1 Mon Sep 17 00:00:00 2001 From: Andrea Canciani Date: Sat, 1 Jun 2024 20:58:27 +0200 Subject: [PATCH 5/6] Improve nullability computation for `CASE WHEN` --- src/EFCore.Relational/Query/SqlNullabilityProcessor.cs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/EFCore.Relational/Query/SqlNullabilityProcessor.cs b/src/EFCore.Relational/Query/SqlNullabilityProcessor.cs index 3bba37c6f2e..988e62e9ad0 100644 --- a/src/EFCore.Relational/Query/SqlNullabilityProcessor.cs +++ b/src/EFCore.Relational/Query/SqlNullabilityProcessor.cs @@ -498,9 +498,7 @@ protected virtual SqlExpression VisitAtTimeZone( /// An optimized sql expression. protected virtual SqlExpression VisitCase(CaseExpression caseExpression, bool allowOptimizedExpansion, out bool nullable) { - // if there is no 'else' there is a possibility of null, when none of the conditions are met - // otherwise the result is nullable if any of the WhenClause results OR ElseResult is nullable - nullable = caseExpression.ElseResult == null; + nullable = false; var currentNonNullableColumnsCount = _nonNullableColumns.Count; var currentNullValueColumnsCount = _nullValueColumns.Count; @@ -558,6 +556,10 @@ protected virtual SqlExpression VisitCase(CaseExpression caseExpression, bool al { elseResult = Visit(caseExpression.ElseResult, out var elseResultNullable); nullable |= elseResultNullable; + + // if there is no 'else' there is a possibility of null, when none of the conditions are met + // otherwise the result is nullable if any of the WhenClause results OR ElseResult is nullable + nullable |= elseResult == null; } RestoreNonNullableColumnsList(currentNonNullableColumnsCount); From a656e25c8b746755bf66d226ef8df9647918fd74 Mon Sep 17 00:00:00 2001 From: Andrea Canciani Date: Sat, 1 Jun 2024 20:58:36 +0200 Subject: [PATCH 6/6] Update tests --- .../Query/JsonQuerySqlServerTest.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/JsonQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/JsonQuerySqlServerTest.cs index 16831967339..b14b92b1813 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/JsonQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/JsonQuerySqlServerTest.cs @@ -1218,7 +1218,7 @@ FROM OPENJSON([j].[OwnedCollectionRoot], '$') AS [o] OUTER APPLY ( SELECT [j].[Id], CAST(JSON_VALUE([o0].[value], '$.Date') AS datetime2) AS [Date], CAST(JSON_VALUE([o0].[value], '$.Enum') AS int) AS [Enum], JSON_QUERY([o0].[value], '$.Enums') AS [Enums], CAST(JSON_VALUE([o0].[value], '$.Fraction') AS decimal(18,2)) AS [Fraction], CAST(JSON_VALUE([o0].[value], '$.NullableEnum') AS int) AS [NullableEnum], JSON_QUERY([o0].[value], '$.NullableEnums') AS [NullableEnums], JSON_QUERY([o0].[value], '$.OwnedCollectionLeaf') AS [c], JSON_QUERY([o0].[value], '$.OwnedReferenceLeaf') AS [c0], [o0].[key], CAST([o0].[key] AS int) AS [c1] FROM OPENJSON(JSON_QUERY([o].[value], '$.OwnedCollectionBranch'), '$') AS [o0] - WHERE CAST(JSON_VALUE([o0].[value], '$.Date') AS datetime2) <> '2000-01-01T00:00:00.0000000' OR CAST(JSON_VALUE([o0].[value], '$.Date') AS datetime2) IS NULL + WHERE CAST(JSON_VALUE([o0].[value], '$.Date') AS datetime2) <> '2000-01-01T00:00:00.0000000' ) AS [o1] ) AS [s] ORDER BY [j].[Id], [s].[c1], [s].[key], [s].[c10] @@ -1373,7 +1373,7 @@ FROM OPENJSON([j].[OwnedCollectionRoot], '$') AS [o2] OUTER APPLY ( SELECT [j].[Id], CAST(JSON_VALUE([o3].[value], '$.Date') AS datetime2) AS [Date], CAST(JSON_VALUE([o3].[value], '$.Enum') AS int) AS [Enum], JSON_QUERY([o3].[value], '$.Enums') AS [Enums], CAST(JSON_VALUE([o3].[value], '$.Fraction') AS decimal(18,2)) AS [Fraction], CAST(JSON_VALUE([o3].[value], '$.NullableEnum') AS int) AS [NullableEnum], JSON_QUERY([o3].[value], '$.NullableEnums') AS [NullableEnums], JSON_QUERY([o3].[value], '$.OwnedCollectionLeaf') AS [c], JSON_QUERY([o3].[value], '$.OwnedReferenceLeaf') AS [c0], [o3].[key], CAST([o3].[key] AS int) AS [c1] FROM OPENJSON(JSON_QUERY([o2].[value], '$.OwnedCollectionBranch'), '$') AS [o3] - WHERE CAST(JSON_VALUE([o3].[value], '$.Date') AS datetime2) <> '2000-01-01T00:00:00.0000000' OR CAST(JSON_VALUE([o3].[value], '$.Date') AS datetime2) IS NULL + WHERE CAST(JSON_VALUE([o3].[value], '$.Date') AS datetime2) <> '2000-01-01T00:00:00.0000000' ) AS [o5] ) AS [s] LEFT JOIN [JsonEntitiesBasicForCollection] AS [j0] ON [j].[Id] = [j0].[ParentId]