diff --git a/src/EFCore/Query/Internal/NavigationExpandingExpressionVisitor.cs b/src/EFCore/Query/Internal/NavigationExpandingExpressionVisitor.cs index 07a042ad3eb..1ad9cd9124b 100644 --- a/src/EFCore/Query/Internal/NavigationExpandingExpressionVisitor.cs +++ b/src/EFCore/Query/Internal/NavigationExpandingExpressionVisitor.cs @@ -15,6 +15,24 @@ namespace Microsoft.EntityFrameworkCore.Query.Internal; /// public partial class NavigationExpandingExpressionVisitor : ExpressionVisitor { + /// + /// 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 readonly bool UseOldBehavior32217 = + AppContext.TryGetSwitch("Microsoft.EntityFrameworkCore.Issue32217", out var enabled32217) && enabled32217; + + /// + /// 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 readonly bool UseOldBehavior32312 = + AppContext.TryGetSwitch("Microsoft.EntityFrameworkCore.Issue32312", out var enabled32312) && enabled32312; + private static readonly PropertyInfo QueryContextContextPropertyInfo = typeof(QueryContext).GetTypeInfo().GetDeclaredProperty(nameof(QueryContext.Context))!; @@ -921,6 +939,11 @@ private Expression ProcessContains(NavigationExpansionExpression source, Express source = (NavigationExpansionExpression)_pendingSelectorExpandingExpressionVisitor.Visit(source); var queryable = Reduce(source); + if (!UseOldBehavior32217) + { + item = Visit(item); + } + return Expression.Call(QueryableMethods.Contains.MakeGenericMethod(queryable.Type.GetSequenceType()), queryable, item); } @@ -959,11 +982,16 @@ private NavigationExpansionExpression ProcessDistinct(NavigationExpansionExpress return new NavigationExpansionExpression(result, navigationTree, navigationTree, parameterName); } - private static NavigationExpansionExpression ProcessSkipTake( + private NavigationExpansionExpression ProcessSkipTake( NavigationExpansionExpression source, MethodInfo genericMethod, Expression count) { + if (!UseOldBehavior32312) + { + count = Visit(count); + } + source.UpdateSource(Expression.Call(genericMethod.MakeGenericMethod(source.SourceElementType), source.Source, count)); return source; @@ -1002,6 +1030,11 @@ private NavigationExpansionExpression ProcessElementAt( source.ApplySelector(Expression.Convert(source.PendingSelector, returnType)); } + if (!UseOldBehavior32312) + { + index = Visit(index); + } + source.ConvertToSingleResult(genericMethod, index); return source; @@ -1560,11 +1593,16 @@ private GroupByNavigationExpansionExpression ProcessOrderByThenBy( return new NavigationExpansionExpression(newSource, navigationTree, navigationTree, parameterName); } - private static GroupByNavigationExpansionExpression ProcessSkipTake( + private GroupByNavigationExpansionExpression ProcessSkipTake( GroupByNavigationExpansionExpression groupBySource, MethodInfo genericMethod, Expression count) { + if (!UseOldBehavior32312) + { + count = Visit(count); + } + groupBySource.UpdateSource( Expression.Call(genericMethod.MakeGenericMethod(groupBySource.SourceElementType), groupBySource.Source, count)); diff --git a/test/EFCore.InMemory.FunctionalTests/Query/GearsOfWarQueryInMemoryTest.cs b/test/EFCore.InMemory.FunctionalTests/Query/GearsOfWarQueryInMemoryTest.cs index 8c1c21a1f66..0d1de5d6be7 100644 --- a/test/EFCore.InMemory.FunctionalTests/Query/GearsOfWarQueryInMemoryTest.cs +++ b/test/EFCore.InMemory.FunctionalTests/Query/GearsOfWarQueryInMemoryTest.cs @@ -136,4 +136,7 @@ public override Task Where_subquery_with_ElementAt_using_column_as_index(bool as public override Task Where_compare_anonymous_types(bool async) => Task.CompletedTask; + + public override Task Subquery_inside_Take_argument(bool async) + => Task.CompletedTask; } diff --git a/test/EFCore.Specification.Tests/Query/GearsOfWarQueryTestBase.cs b/test/EFCore.Specification.Tests/Query/GearsOfWarQueryTestBase.cs index 152ab14c8dd..1f2bcbe5c48 100644 --- a/test/EFCore.Specification.Tests/Query/GearsOfWarQueryTestBase.cs +++ b/test/EFCore.Specification.Tests/Query/GearsOfWarQueryTestBase.cs @@ -8286,6 +8286,87 @@ public virtual Task Set_operator_with_navigation_in_projection_groupby_aggregate .GroupBy(x => new { x.Name }) .Select(x => new { x.Key.Name, SumOfLengths = x.Sum(xx => xx.Location.Length) })); + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Nav_expansion_inside_Contains_argument(bool async) + { + var numbers = new[] { 1, -1 }; + + return AssertQuery( + async, + ss => ss.Set().Where(x => numbers.Contains(x.Weapons.Any() ? 1 : 0))); + } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Nav_expansion_with_member_pushdown_inside_Contains_argument(bool async) + { + var weapons = new[] { "Marcus' Lancer", "Dom's Gnasher" }; + + return AssertQuery( + async, + ss => ss.Set().Where(x => weapons.Contains(x.Weapons.OrderBy(w => w.Id).FirstOrDefault().Name))); + } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Subquery_inside_Take_argument(bool async) + { + var numbers = new[] { 0, 1, 2 }; + + return AssertQuery( + async, + ss => ss.Set().OrderBy(x => x.Nickname).Select( + x => x.Weapons.OrderBy(g => g.Id).Take(numbers.OrderBy(xx => xx).Skip(1).FirstOrDefault())), + assertOrder: true, + elementAsserter: (e, a) => AssertCollection(e, a, ordered: true)); + } + + [ConditionalTheory(Skip = "issue #32303")] + [MemberData(nameof(IsAsyncData))] + public virtual Task Nav_expansion_inside_Skip_correlated_to_source(bool async) + { + return AssertQuery( + async, + ss => ss.Set().OrderBy(x => x.Name).Select( + x => x.BornGears.OrderBy(g => g.FullName).Skip(x.StationedGears.Any() ? 1 : 0))); + } + + [ConditionalTheory(Skip = "issue #32303")] + [MemberData(nameof(IsAsyncData))] + public virtual Task Nav_expansion_inside_Take_correlated_to_source(bool async) + { + return AssertQuery( + async, + ss => ss.Set().OrderBy(x => x.Nickname).Select( + x => x.Weapons.OrderBy(g => g.Id).Take(x.AssignedCity.Name.Length))); + } + + [ConditionalTheory(Skip = "issue #32303")] + [MemberData(nameof(IsAsyncData))] + public virtual Task Nav_expansion_with_member_pushdown_inside_Take_correlated_to_source(bool async) + { + var numbers = new[] { 0, 1, 2 }; + + return AssertQuery( + async, + ss => ss.Set().OrderBy(x => x.Nickname).Select( + x => x.Weapons.OrderBy(g => g.Id).Take( + ss.Set().OrderBy(xx => xx.Nickname).FirstOrDefault().AssignedCity.Name.Length)), + assertOrder: true, + elementAsserter: (e, a) => AssertCollection(e, a, ordered: true)); + } + + [ConditionalTheory(Skip = "issue #32303")] + [MemberData(nameof(IsAsyncData))] + public virtual Task Nav_expansion_inside_ElementAt_correlated_to_source(bool async) + { + return AssertQuery( + async, + ss => ss.Set().OrderBy(x => x.Nickname).Select( + x => x.Weapons.OrderBy(g => g.Id).ElementAt(x.AssignedCity != null ? 1 : 0))); + } + protected GearsOfWarContext CreateContext() => Fixture.CreateContext(); diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/GearsOfWarQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/GearsOfWarQuerySqlServerTest.cs index 75121329252..6d16d71b713 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/GearsOfWarQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/GearsOfWarQuerySqlServerTest.cs @@ -10151,6 +10151,108 @@ GROUP BY [s].[Name] """); } + public override async Task Nav_expansion_inside_Contains_argument(bool async) + { + await base.Nav_expansion_inside_Contains_argument(async); + + AssertSql( +""" +@__numbers_0='[1,-1]' (Size = 4000) + +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 CASE + WHEN EXISTS ( + SELECT 1 + FROM [Weapons] AS [w] + WHERE [g].[FullName] = [w].[OwnerFullName]) THEN 1 + ELSE 0 +END IN ( + SELECT [n].[value] + FROM OPENJSON(@__numbers_0) WITH ([value] int '$') AS [n] +) +"""); + } + + public override async Task Nav_expansion_with_member_pushdown_inside_Contains_argument(bool async) + { + await base.Nav_expansion_with_member_pushdown_inside_Contains_argument(async); + + AssertSql( +""" +@__weapons_0='["Marcus\u0027 Lancer","Dom\u0027s Gnasher"]' (Size = 4000) + +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 EXISTS ( + SELECT 1 + FROM OPENJSON(@__weapons_0) WITH ([value] nvarchar(max) '$') AS [w0] + WHERE [w0].[value] = ( + SELECT TOP(1) [w].[Name] + FROM [Weapons] AS [w] + WHERE [g].[FullName] = [w].[OwnerFullName] + ORDER BY [w].[Id]) OR ([w0].[value] IS NULL AND ( + SELECT TOP(1) [w].[Name] + FROM [Weapons] AS [w] + WHERE [g].[FullName] = [w].[OwnerFullName] + ORDER BY [w].[Id]) IS NULL)) +"""); + } + + public override async Task Subquery_inside_Take_argument(bool async) + { + await base.Subquery_inside_Take_argument(async); + + AssertSql( +""" +@__numbers_0='[0,1,2]' (Size = 4000) + +SELECT [g].[Nickname], [g].[SquadId], [t0].[Id], [t0].[AmmunitionType], [t0].[IsAutomatic], [t0].[Name], [t0].[OwnerFullName], [t0].[SynergyWithId] +FROM [Gears] AS [g] +LEFT JOIN ( + SELECT [t].[Id], [t].[AmmunitionType], [t].[IsAutomatic], [t].[Name], [t].[OwnerFullName], [t].[SynergyWithId] + FROM ( + SELECT [w].[Id], [w].[AmmunitionType], [w].[IsAutomatic], [w].[Name], [w].[OwnerFullName], [w].[SynergyWithId], ROW_NUMBER() OVER(PARTITION BY [w].[OwnerFullName] ORDER BY [w].[Id]) AS [row] + FROM [Weapons] AS [w] + ) AS [t] + WHERE [t].[row] <= COALESCE(( + SELECT [n].[value] + FROM OPENJSON(@__numbers_0) WITH ([value] int '$') AS [n] + ORDER BY [n].[value] + OFFSET 1 ROWS FETCH NEXT 1 ROWS ONLY), 0) +) AS [t0] ON [g].[FullName] = [t0].[OwnerFullName] +ORDER BY [g].[Nickname], [g].[SquadId], [t0].[OwnerFullName], [t0].[Id] +"""); + } + + public override async Task Nav_expansion_inside_Skip_correlated_to_source(bool async) + { + await base.Nav_expansion_inside_Skip_correlated_to_source(async); + + AssertSql(); + } + + public override async Task Nav_expansion_inside_Take_correlated_to_source(bool async) + { + await base.Nav_expansion_inside_Take_correlated_to_source(async); + + AssertSql(); + } + + public override async Task Nav_expansion_with_member_pushdown_inside_Take_correlated_to_source(bool async) + { + await base.Nav_expansion_with_member_pushdown_inside_Take_correlated_to_source(async); + + AssertSql(); + } + + public override async Task Nav_expansion_inside_ElementAt_correlated_to_source(bool async) + { + await base.Nav_expansion_inside_ElementAt_correlated_to_source(async); + + AssertSql(); + } + private void AssertSql(params string[] expected) => Fixture.TestSqlLoggerFactory.AssertBaseline(expected); } diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/TPCGearsOfWarQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/TPCGearsOfWarQuerySqlServerTest.cs index 7a6806fd36f..d5ce36064db 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/TPCGearsOfWarQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/TPCGearsOfWarQuerySqlServerTest.cs @@ -13365,6 +13365,126 @@ GROUP BY [s].[Name] """); } + public override async Task Nav_expansion_inside_Contains_argument(bool async) + { + await base.Nav_expansion_inside_Contains_argument(async); + + AssertSql( +""" +@__numbers_0='[1,-1]' (Size = 4000) + +SELECT [t].[Nickname], [t].[SquadId], [t].[AssignedCityName], [t].[CityOfBirthName], [t].[FullName], [t].[HasSoulPatch], [t].[LeaderNickname], [t].[LeaderSquadId], [t].[Rank], [t].[Discriminator] +FROM ( + SELECT [g].[Nickname], [g].[SquadId], [g].[AssignedCityName], [g].[CityOfBirthName], [g].[FullName], [g].[HasSoulPatch], [g].[LeaderNickname], [g].[LeaderSquadId], [g].[Rank], N'Gear' AS [Discriminator] + FROM [Gears] AS [g] + 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 [t] +WHERE CASE + WHEN EXISTS ( + SELECT 1 + FROM [Weapons] AS [w] + WHERE [t].[FullName] = [w].[OwnerFullName]) THEN 1 + ELSE 0 +END IN ( + SELECT [n].[value] + FROM OPENJSON(@__numbers_0) WITH ([value] int '$') AS [n] +) +"""); + } + + public override async Task Nav_expansion_with_member_pushdown_inside_Contains_argument(bool async) + { + await base.Nav_expansion_with_member_pushdown_inside_Contains_argument(async); + + AssertSql( +""" +@__weapons_0='["Marcus\u0027 Lancer","Dom\u0027s Gnasher"]' (Size = 4000) + +SELECT [t].[Nickname], [t].[SquadId], [t].[AssignedCityName], [t].[CityOfBirthName], [t].[FullName], [t].[HasSoulPatch], [t].[LeaderNickname], [t].[LeaderSquadId], [t].[Rank], [t].[Discriminator] +FROM ( + SELECT [g].[Nickname], [g].[SquadId], [g].[AssignedCityName], [g].[CityOfBirthName], [g].[FullName], [g].[HasSoulPatch], [g].[LeaderNickname], [g].[LeaderSquadId], [g].[Rank], N'Gear' AS [Discriminator] + FROM [Gears] AS [g] + 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 [t] +WHERE EXISTS ( + SELECT 1 + FROM OPENJSON(@__weapons_0) WITH ([value] nvarchar(max) '$') AS [w0] + WHERE [w0].[value] = ( + SELECT TOP(1) [w].[Name] + FROM [Weapons] AS [w] + WHERE [t].[FullName] = [w].[OwnerFullName] + ORDER BY [w].[Id]) OR ([w0].[value] IS NULL AND ( + SELECT TOP(1) [w].[Name] + FROM [Weapons] AS [w] + WHERE [t].[FullName] = [w].[OwnerFullName] + ORDER BY [w].[Id]) IS NULL)) +"""); + } + + public override async Task Subquery_inside_Take_argument(bool async) + { + await base.Subquery_inside_Take_argument(async); + + AssertSql( +""" +@__numbers_0='[0,1,2]' (Size = 4000) + +SELECT [t].[Nickname], [t].[SquadId], [t0].[Id], [t0].[AmmunitionType], [t0].[IsAutomatic], [t0].[Name], [t0].[OwnerFullName], [t0].[SynergyWithId] +FROM ( + SELECT [g].[Nickname], [g].[SquadId], [g].[FullName] + FROM [Gears] AS [g] + UNION ALL + SELECT [o].[Nickname], [o].[SquadId], [o].[FullName] + FROM [Officers] AS [o] +) AS [t] +LEFT JOIN ( + SELECT [t1].[Id], [t1].[AmmunitionType], [t1].[IsAutomatic], [t1].[Name], [t1].[OwnerFullName], [t1].[SynergyWithId] + FROM ( + SELECT [w].[Id], [w].[AmmunitionType], [w].[IsAutomatic], [w].[Name], [w].[OwnerFullName], [w].[SynergyWithId], ROW_NUMBER() OVER(PARTITION BY [w].[OwnerFullName] ORDER BY [w].[Id]) AS [row] + FROM [Weapons] AS [w] + ) AS [t1] + WHERE [t1].[row] <= COALESCE(( + SELECT [n].[value] + FROM OPENJSON(@__numbers_0) WITH ([value] int '$') AS [n] + ORDER BY [n].[value] + OFFSET 1 ROWS FETCH NEXT 1 ROWS ONLY), 0) +) AS [t0] ON [t].[FullName] = [t0].[OwnerFullName] +ORDER BY [t].[Nickname], [t].[SquadId], [t0].[OwnerFullName], [t0].[Id] +"""); + } + + public override async Task Nav_expansion_inside_Skip_correlated_to_source(bool async) + { + await base.Nav_expansion_inside_Skip_correlated_to_source(async); + + AssertSql(); + } + + public override async Task Nav_expansion_inside_Take_correlated_to_source(bool async) + { + await base.Nav_expansion_inside_Take_correlated_to_source(async); + + AssertSql(); + } + + public override async Task Nav_expansion_with_member_pushdown_inside_Take_correlated_to_source(bool async) + { + await base.Nav_expansion_with_member_pushdown_inside_Take_correlated_to_source(async); + + AssertSql(); + } + + public override async Task Nav_expansion_inside_ElementAt_correlated_to_source(bool async) + { + await base.Nav_expansion_inside_ElementAt_correlated_to_source(async); + + AssertSql(); + } + private void AssertSql(params string[] expected) => Fixture.TestSqlLoggerFactory.AssertBaseline(expected); } diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/TPTGearsOfWarQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/TPTGearsOfWarQuerySqlServerTest.cs index a12560dd9dd..bbc6385fe39 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/TPTGearsOfWarQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/TPTGearsOfWarQuerySqlServerTest.cs @@ -11508,6 +11508,114 @@ GROUP BY [s].[Name] """); } + public override async Task Nav_expansion_inside_Contains_argument(bool async) + { + await base.Nav_expansion_inside_Contains_argument(async); + + AssertSql( +""" +@__numbers_0='[1,-1]' (Size = 4000) + +SELECT [g].[Nickname], [g].[SquadId], [g].[AssignedCityName], [g].[CityOfBirthName], [g].[FullName], [g].[HasSoulPatch], [g].[LeaderNickname], [g].[LeaderSquadId], [g].[Rank], CASE + 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 CASE + WHEN EXISTS ( + SELECT 1 + FROM [Weapons] AS [w] + WHERE [g].[FullName] = [w].[OwnerFullName]) THEN 1 + ELSE 0 +END IN ( + SELECT [n].[value] + FROM OPENJSON(@__numbers_0) WITH ([value] int '$') AS [n] +) +"""); + } + + public override async Task Nav_expansion_with_member_pushdown_inside_Contains_argument(bool async) + { + await base.Nav_expansion_with_member_pushdown_inside_Contains_argument(async); + + AssertSql( +""" +@__weapons_0='["Marcus\u0027 Lancer","Dom\u0027s Gnasher"]' (Size = 4000) + +SELECT [g].[Nickname], [g].[SquadId], [g].[AssignedCityName], [g].[CityOfBirthName], [g].[FullName], [g].[HasSoulPatch], [g].[LeaderNickname], [g].[LeaderSquadId], [g].[Rank], CASE + 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 EXISTS ( + SELECT 1 + FROM OPENJSON(@__weapons_0) WITH ([value] nvarchar(max) '$') AS [w0] + WHERE [w0].[value] = ( + SELECT TOP(1) [w].[Name] + FROM [Weapons] AS [w] + WHERE [g].[FullName] = [w].[OwnerFullName] + ORDER BY [w].[Id]) OR ([w0].[value] IS NULL AND ( + SELECT TOP(1) [w].[Name] + FROM [Weapons] AS [w] + WHERE [g].[FullName] = [w].[OwnerFullName] + ORDER BY [w].[Id]) IS NULL)) +"""); + } + + public override async Task Subquery_inside_Take_argument(bool async) + { + await base.Subquery_inside_Take_argument(async); + + AssertSql( +""" +@__numbers_0='[0,1,2]' (Size = 4000) + +SELECT [g].[Nickname], [g].[SquadId], [t0].[Id], [t0].[AmmunitionType], [t0].[IsAutomatic], [t0].[Name], [t0].[OwnerFullName], [t0].[SynergyWithId] +FROM [Gears] AS [g] +LEFT JOIN ( + SELECT [t].[Id], [t].[AmmunitionType], [t].[IsAutomatic], [t].[Name], [t].[OwnerFullName], [t].[SynergyWithId] + FROM ( + SELECT [w].[Id], [w].[AmmunitionType], [w].[IsAutomatic], [w].[Name], [w].[OwnerFullName], [w].[SynergyWithId], ROW_NUMBER() OVER(PARTITION BY [w].[OwnerFullName] ORDER BY [w].[Id]) AS [row] + FROM [Weapons] AS [w] + ) AS [t] + WHERE [t].[row] <= COALESCE(( + SELECT [n].[value] + FROM OPENJSON(@__numbers_0) WITH ([value] int '$') AS [n] + ORDER BY [n].[value] + OFFSET 1 ROWS FETCH NEXT 1 ROWS ONLY), 0) +) AS [t0] ON [g].[FullName] = [t0].[OwnerFullName] +ORDER BY [g].[Nickname], [g].[SquadId], [t0].[OwnerFullName], [t0].[Id] +"""); + } + + public override async Task Nav_expansion_inside_Skip_correlated_to_source(bool async) + { + await base.Nav_expansion_inside_Skip_correlated_to_source(async); + + AssertSql(); + } + + public override async Task Nav_expansion_inside_Take_correlated_to_source(bool async) + { + await base.Nav_expansion_inside_Take_correlated_to_source(async); + + AssertSql(); + } + + public override async Task Nav_expansion_with_member_pushdown_inside_Take_correlated_to_source(bool async) + { + await base.Nav_expansion_with_member_pushdown_inside_Take_correlated_to_source(async); + + AssertSql(); + } + + public override async Task Nav_expansion_inside_ElementAt_correlated_to_source(bool async) + { + await base.Nav_expansion_inside_ElementAt_correlated_to_source(async); + + AssertSql(); + } + private void AssertSql(params string[] expected) => Fixture.TestSqlLoggerFactory.AssertBaseline(expected); } diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/TemporalGearsOfWarQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/TemporalGearsOfWarQuerySqlServerTest.cs index 590596a180d..c32b0178387 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/TemporalGearsOfWarQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/TemporalGearsOfWarQuerySqlServerTest.cs @@ -10059,6 +10059,108 @@ GROUP BY [s].[Name] """); } + public override async Task Nav_expansion_inside_Contains_argument(bool async) + { + await base.Nav_expansion_inside_Contains_argument(async); + + AssertSql( +""" +@__numbers_0='[1,-1]' (Size = 4000) + +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 CASE + WHEN EXISTS ( + SELECT 1 + FROM [Weapons] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [w] + WHERE [g].[FullName] = [w].[OwnerFullName]) THEN 1 + ELSE 0 +END IN ( + SELECT [n].[value] + FROM OPENJSON(@__numbers_0) WITH ([value] int '$') AS [n] +) +"""); + } + + public override async Task Nav_expansion_with_member_pushdown_inside_Contains_argument(bool async) + { + await base.Nav_expansion_with_member_pushdown_inside_Contains_argument(async); + + AssertSql( +""" +@__weapons_0='["Marcus\u0027 Lancer","Dom\u0027s Gnasher"]' (Size = 4000) + +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 EXISTS ( + SELECT 1 + FROM OPENJSON(@__weapons_0) WITH ([value] nvarchar(max) '$') AS [w0] + WHERE [w0].[value] = ( + SELECT TOP(1) [w].[Name] + FROM [Weapons] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [w] + WHERE [g].[FullName] = [w].[OwnerFullName] + ORDER BY [w].[Id]) OR ([w0].[value] IS NULL AND ( + SELECT TOP(1) [w].[Name] + FROM [Weapons] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [w] + WHERE [g].[FullName] = [w].[OwnerFullName] + ORDER BY [w].[Id]) IS NULL)) +"""); + } + + public override async Task Subquery_inside_Take_argument(bool async) + { + await base.Subquery_inside_Take_argument(async); + + AssertSql( +""" +@__numbers_0='[0,1,2]' (Size = 4000) + +SELECT [g].[Nickname], [g].[SquadId], [t0].[Id], [t0].[AmmunitionType], [t0].[IsAutomatic], [t0].[Name], [t0].[OwnerFullName], [t0].[PeriodEnd], [t0].[PeriodStart], [t0].[SynergyWithId] +FROM [Gears] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [g] +LEFT JOIN ( + SELECT [t].[Id], [t].[AmmunitionType], [t].[IsAutomatic], [t].[Name], [t].[OwnerFullName], [t].[PeriodEnd], [t].[PeriodStart], [t].[SynergyWithId] + FROM ( + SELECT [w].[Id], [w].[AmmunitionType], [w].[IsAutomatic], [w].[Name], [w].[OwnerFullName], [w].[PeriodEnd], [w].[PeriodStart], [w].[SynergyWithId], ROW_NUMBER() OVER(PARTITION BY [w].[OwnerFullName] ORDER BY [w].[Id]) AS [row] + FROM [Weapons] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [w] + ) AS [t] + WHERE [t].[row] <= COALESCE(( + SELECT [n].[value] + FROM OPENJSON(@__numbers_0) WITH ([value] int '$') AS [n] + ORDER BY [n].[value] + OFFSET 1 ROWS FETCH NEXT 1 ROWS ONLY), 0) +) AS [t0] ON [g].[FullName] = [t0].[OwnerFullName] +ORDER BY [g].[Nickname], [g].[SquadId], [t0].[OwnerFullName], [t0].[Id] +"""); + } + + public override async Task Nav_expansion_inside_Skip_correlated_to_source(bool async) + { + await base.Nav_expansion_inside_Skip_correlated_to_source(async); + + AssertSql(); + } + + public override async Task Nav_expansion_inside_Take_correlated_to_source(bool async) + { + await base.Nav_expansion_inside_Take_correlated_to_source(async); + + AssertSql(); + } + + public override async Task Nav_expansion_with_member_pushdown_inside_Take_correlated_to_source(bool async) + { + await base.Nav_expansion_with_member_pushdown_inside_Take_correlated_to_source(async); + + AssertSql(); + } + + public override async Task Nav_expansion_inside_ElementAt_correlated_to_source(bool async) + { + await base.Nav_expansion_inside_ElementAt_correlated_to_source(async); + + AssertSql(); + } + private void AssertSql(params string[] expected) => Fixture.TestSqlLoggerFactory.AssertBaseline(expected); } diff --git a/test/EFCore.Sqlite.FunctionalTests/Query/GearsOfWarQuerySqliteTest.cs b/test/EFCore.Sqlite.FunctionalTests/Query/GearsOfWarQuerySqliteTest.cs index 2cb6e0ac610..1315218c721 100644 --- a/test/EFCore.Sqlite.FunctionalTests/Query/GearsOfWarQuerySqliteTest.cs +++ b/test/EFCore.Sqlite.FunctionalTests/Query/GearsOfWarQuerySqliteTest.cs @@ -9520,6 +9520,110 @@ GROUP BY "s"."Name" """); } + public override async Task Nav_expansion_inside_Contains_argument(bool async) + { + await base.Nav_expansion_inside_Contains_argument(async); + + AssertSql( +""" +@__numbers_0='[1,-1]' (Size = 6) + +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 CASE + WHEN EXISTS ( + SELECT 1 + FROM "Weapons" AS "w" + WHERE "g"."FullName" = "w"."OwnerFullName") THEN 1 + ELSE 0 +END IN ( + SELECT "n"."value" + FROM json_each(@__numbers_0) AS "n" +) +"""); + } + + public override async Task Nav_expansion_with_member_pushdown_inside_Contains_argument(bool async) + { + await base.Nav_expansion_with_member_pushdown_inside_Contains_argument(async); + + AssertSql( +""" +@__weapons_0='["Marcus\u0027 Lancer","Dom\u0027s Gnasher"]' (Size = 44) + +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 EXISTS ( + SELECT 1 + FROM json_each(@__weapons_0) AS "w0" + WHERE "w0"."value" = ( + SELECT "w"."Name" + FROM "Weapons" AS "w" + WHERE "g"."FullName" = "w"."OwnerFullName" + ORDER BY "w"."Id" + LIMIT 1) OR ("w0"."value" IS NULL AND ( + SELECT "w"."Name" + FROM "Weapons" AS "w" + WHERE "g"."FullName" = "w"."OwnerFullName" + ORDER BY "w"."Id" + LIMIT 1) IS NULL)) +"""); + } + + public override async Task Subquery_inside_Take_argument(bool async) + { + await base.Subquery_inside_Take_argument(async); + + AssertSql( +""" +@__numbers_0='[0,1,2]' (Size = 7) + +SELECT "g"."Nickname", "g"."SquadId", "t0"."Id", "t0"."AmmunitionType", "t0"."IsAutomatic", "t0"."Name", "t0"."OwnerFullName", "t0"."SynergyWithId" +FROM "Gears" AS "g" +LEFT JOIN ( + SELECT "t"."Id", "t"."AmmunitionType", "t"."IsAutomatic", "t"."Name", "t"."OwnerFullName", "t"."SynergyWithId" + FROM ( + SELECT "w"."Id", "w"."AmmunitionType", "w"."IsAutomatic", "w"."Name", "w"."OwnerFullName", "w"."SynergyWithId", ROW_NUMBER() OVER(PARTITION BY "w"."OwnerFullName" ORDER BY "w"."Id") AS "row" + FROM "Weapons" AS "w" + ) AS "t" + WHERE "t"."row" <= COALESCE(( + SELECT "n"."value" + FROM json_each(@__numbers_0) AS "n" + ORDER BY "n"."value" + LIMIT 1 OFFSET 1), 0) +) AS "t0" ON "g"."FullName" = "t0"."OwnerFullName" +ORDER BY "g"."Nickname", "g"."SquadId", "t0"."OwnerFullName", "t0"."Id" +"""); + } + + public override async Task Nav_expansion_inside_Skip_correlated_to_source(bool async) + { + await base.Nav_expansion_inside_Skip_correlated_to_source(async); + + AssertSql(); + } + + public override async Task Nav_expansion_inside_Take_correlated_to_source(bool async) + { + await base.Nav_expansion_inside_Take_correlated_to_source(async); + + AssertSql(); + } + + public override async Task Nav_expansion_with_member_pushdown_inside_Take_correlated_to_source(bool async) + { + await base.Nav_expansion_with_member_pushdown_inside_Take_correlated_to_source(async); + + AssertSql(); + } + + public override async Task Nav_expansion_inside_ElementAt_correlated_to_source(bool async) + { + await base.Nav_expansion_inside_ElementAt_correlated_to_source(async); + + AssertSql(); + } + public override Task DateTimeOffset_to_unix_time_milliseconds(bool async) => AssertTranslationFailed(() => base.DateTimeOffset_to_unix_time_milliseconds(async));