diff --git a/src/EFCore.Relational/Properties/RelationalStrings.Designer.cs b/src/EFCore.Relational/Properties/RelationalStrings.Designer.cs
index 5de1ff3e3fa..fcc660d5a5f 100644
--- a/src/EFCore.Relational/Properties/RelationalStrings.Designer.cs
+++ b/src/EFCore.Relational/Properties/RelationalStrings.Designer.cs
@@ -1779,6 +1779,14 @@ public static string StoredKeyTypesNotConvertable(object? fkColumnName, object?
GetString("StoredKeyTypesNotConvertable", nameof(fkColumnName), nameof(fkColumnType), nameof(pkColumnType), nameof(pkColumnName)),
fkColumnName, fkColumnType, pkColumnType, pkColumnName);
+ ///
+ /// Expression type '{expressionType}' does not implement proper cloning logic. Every expression derived from '{tableExpressionBase}' must implement '{clonableTableExpressionBase}' interface or have it's cloning logic added to the '{cloningExpressionVisitor}' inside '{selectExpression}'.
+ ///
+ public static string TableExpressionBaseWithoutCloningLogic(object? expressionType, object? tableExpressionBase, object? clonableTableExpressionBase, object? cloningExpressionVisitor, object? selectExpression)
+ => string.Format(
+ GetString("TableExpressionBaseWithoutCloningLogic", nameof(expressionType), nameof(tableExpressionBase), nameof(clonableTableExpressionBase), nameof(cloningExpressionVisitor), nameof(selectExpression)),
+ expressionType, tableExpressionBase, clonableTableExpressionBase, cloningExpressionVisitor, selectExpression);
+
///
/// The entity type '{entityType}' is not mapped to the store object '{table}'.
///
diff --git a/src/EFCore.Relational/Properties/RelationalStrings.resx b/src/EFCore.Relational/Properties/RelationalStrings.resx
index e89cace8c09..128be2b2177 100644
--- a/src/EFCore.Relational/Properties/RelationalStrings.resx
+++ b/src/EFCore.Relational/Properties/RelationalStrings.resx
@@ -1094,6 +1094,9 @@
The entity type '{entityType}' was configured to use some stored procedures and is not mapped to any table. An entity type that isn't mapped to a table must be mapped to insert, update and delete stored procedures.
+
+ Expression type '{expressionType}' does not implement proper cloning logic. Every expression derived from '{tableExpressionBase}' must implement '{clonableTableExpressionBase}' interface or have it's cloning logic added to the '{cloningExpressionVisitor}' inside '{selectExpression}'.
+
The entity type '{entityType}' is not mapped to the store object '{table}'.
diff --git a/src/EFCore.Relational/Query/SqlExpressions/SelectExpression.Helper.cs b/src/EFCore.Relational/Query/SqlExpressions/SelectExpression.Helper.cs
index ec9dd339dd7..a9949af0d0d 100644
--- a/src/EFCore.Relational/Query/SqlExpressions/SelectExpression.Helper.cs
+++ b/src/EFCore.Relational/Query/SqlExpressions/SelectExpression.Helper.cs
@@ -2,6 +2,7 @@
// The .NET Foundation licenses this file to you under the MIT license.
using System.Diagnostics.CodeAnalysis;
+using Microsoft.EntityFrameworkCore.Metadata.Internal;
namespace Microsoft.EntityFrameworkCore.Query.SqlExpressions;
@@ -1008,7 +1009,60 @@ private sealed class CloningExpressionVisitor : ExpressionVisitor
return newTpcTable;
}
- return expression is IClonableTableExpressionBase cloneable ? cloneable.Clone() : base.Visit(expression);
+ if (expression is TableValuedFunctionExpression tableValuedFunctionExpression)
+ {
+ var newArguments = new SqlExpression[tableValuedFunctionExpression.Arguments.Count];
+ for (var i = 0; i < newArguments.Length; i++)
+ {
+ newArguments[i] = (SqlExpression)Visit(tableValuedFunctionExpression.Arguments[i]);
+ }
+
+ var newTableValuedFunctionExpression = new TableValuedFunctionExpression(
+ tableValuedFunctionExpression.StoreFunction,
+ newArguments)
+ {
+ Alias = tableValuedFunctionExpression.Alias
+ };
+
+ foreach (var annotation in tableValuedFunctionExpression.GetAnnotations())
+ {
+ newTableValuedFunctionExpression.AddAnnotation(annotation.Name, annotation.Value);
+ }
+
+ return newTableValuedFunctionExpression;
+ }
+
+ if (expression is IClonableTableExpressionBase cloneable)
+ {
+ return cloneable.Clone();
+ }
+
+ // join and set operations are fine, because they contain other TableExpressionBases inside, that will get cloned
+ // and therefore set expression's Update function will generate a new instance.
+ if (expression is CrossJoinExpression
+ or InnerJoinExpression
+ or LeftJoinExpression
+ or CrossApplyExpression
+ or OuterApplyExpression
+ or ExceptExpression
+ or IntersectExpression
+ or UnionExpression)
+ {
+ return base.Visit(expression);
+ }
+
+ if (expression is TableExpressionBase)
+ {
+ throw new InvalidOperationException(
+ RelationalStrings.TableExpressionBaseWithoutCloningLogic(
+ expression.GetType().Name,
+ nameof(TableExpressionBase),
+ nameof(IClonableTableExpressionBase),
+ nameof(CloningExpressionVisitor),
+ nameof(SelectExpression))); ;
+ }
+
+ return base.Visit(expression);
}
}
diff --git a/test/EFCore.Relational.Specification.Tests/Query/UdfDbFunctionTestBase.cs b/test/EFCore.Relational.Specification.Tests/Query/UdfDbFunctionTestBase.cs
index cf8153810e3..980b287dfcd 100644
--- a/test/EFCore.Relational.Specification.Tests/Query/UdfDbFunctionTestBase.cs
+++ b/test/EFCore.Relational.Specification.Tests/Query/UdfDbFunctionTestBase.cs
@@ -2133,6 +2133,52 @@ orderby t.ProductId
}
}
+ [ConditionalFact]
+ public virtual void TVF_with_navigation_in_projection_groupby_aggregate()
+ {
+ using (var context = CreateContext())
+ {
+ var query = context.Orders
+ .Where(c => !context.Set().Select(x => x.ProductId).Contains(25))
+ .Select(x => new { x.Customer.FirstName, x.Customer.LastName })
+ .GroupBy(x => new { x.LastName })
+ .Select(x => new { x.Key.LastName, SumOfLengths = x.Sum(xx => xx.FirstName.Length) })
+ .ToList();
+
+ Assert.Equal(3, query.Count);
+ var orderedResult = query.OrderBy(x => x.LastName).ToList();
+ Assert.Equal("One", orderedResult[0].LastName);
+ Assert.Equal(24, orderedResult[0].SumOfLengths);
+ Assert.Equal("Three", orderedResult[1].LastName);
+ Assert.Equal(8, orderedResult[1].SumOfLengths);
+ Assert.Equal("Two", orderedResult[2].LastName);
+ Assert.Equal(16, orderedResult[2].SumOfLengths);
+ }
+ }
+
+ [ConditionalFact]
+ public virtual void TVF_with_argument_being_a_subquery_with_navigation_in_projection_groupby_aggregate()
+ {
+ using (var context = CreateContext())
+ {
+ var query = context.Orders
+ .Where(c => !context.GetOrdersWithMultipleProducts(context.Customers.OrderBy(x => x.Id).FirstOrDefault().Id).Select(x => x.CustomerId).Contains(25))
+ .Select(x => new { x.Customer.FirstName, x.Customer.LastName })
+ .GroupBy(x => new { x.LastName })
+ .Select(x => new { x.Key.LastName, SumOfLengths = x.Sum(xx => xx.FirstName.Length) })
+ .ToList();
+
+ Assert.Equal(3, query.Count);
+ var orderedResult = query.OrderBy(x => x.LastName).ToList();
+ Assert.Equal("One", orderedResult[0].LastName);
+ Assert.Equal(24, orderedResult[0].SumOfLengths);
+ Assert.Equal("Three", orderedResult[1].LastName);
+ Assert.Equal(8, orderedResult[1].SumOfLengths);
+ Assert.Equal("Two", orderedResult[2].LastName);
+ Assert.Equal(16, orderedResult[2].SumOfLengths);
+ }
+ }
+
[ConditionalFact]
public virtual void TVF_backing_entity_type_mapped_to_view()
{
diff --git a/test/EFCore.Specification.Tests/Query/GearsOfWarQueryTestBase.cs b/test/EFCore.Specification.Tests/Query/GearsOfWarQueryTestBase.cs
index 0810b8140f2..4ad1d8ac4c0 100644
--- a/test/EFCore.Specification.Tests/Query/GearsOfWarQueryTestBase.cs
+++ b/test/EFCore.Specification.Tests/Query/GearsOfWarQueryTestBase.cs
@@ -8240,6 +8240,17 @@ public virtual Task DateTimeOffset_to_unix_time_seconds(bool async)
.FirstOrDefault() == null));
}
+ [ConditionalTheory]
+ [MemberData(nameof(IsAsyncData))]
+ public virtual Task Set_operator_with_navigation_in_projection_groupby_aggregate(bool async)
+ => AssertQuery(
+ async,
+ ss => ss.Set()
+ .Where(x => ss.Set().Concat(ss.Set()).Select(x => x.Nickname).Contains("Marcus"))
+ .Select(x => new { x.Squad.Name, x.CityOfBirth.Location })
+ .GroupBy(x => new { x.Name })
+ .Select(x => new { x.Key.Name, SumOfLengths = x.Sum(xx => xx.Location.Length) }));
+
protected GearsOfWarContext CreateContext()
=> Fixture.CreateContext();
diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/GearsOfWarQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/GearsOfWarQuerySqlServerTest.cs
index 46cf72f8201..ce029b62c64 100644
--- a/test/EFCore.SqlServer.FunctionalTests/Query/GearsOfWarQuerySqlServerTest.cs
+++ b/test/EFCore.SqlServer.FunctionalTests/Query/GearsOfWarQuerySqlServerTest.cs
@@ -10016,6 +10016,43 @@ FROM [SquadMissions] AS [s0]
ORDER BY [g].[Nickname], [g].[SquadId], [s].[Id], [s1].[SquadId]");
}
+ public override async Task Set_operator_with_navigation_in_projection_groupby_aggregate(bool async)
+ {
+ await base.Set_operator_with_navigation_in_projection_groupby_aggregate(async);
+
+ AssertSql(
+"""
+SELECT [s].[Name], (
+ SELECT COALESCE(SUM(CAST(LEN([c].[Location]) AS int)), 0)
+ FROM [Gears] AS [g2]
+ INNER JOIN [Squads] AS [s0] ON [g2].[SquadId] = [s0].[Id]
+ INNER JOIN [Cities] AS [c] ON [g2].[CityOfBirthName] = [c].[Name]
+ WHERE EXISTS (
+ SELECT 1
+ FROM (
+ SELECT [g3].[Nickname], [g3].[SquadId], [g3].[AssignedCityName], [g3].[CityOfBirthName], [g3].[Discriminator], [g3].[FullName], [g3].[HasSoulPatch], [g3].[LeaderNickname], [g3].[LeaderSquadId], [g3].[Rank]
+ FROM [Gears] AS [g3]
+ UNION ALL
+ SELECT [g4].[Nickname], [g4].[SquadId], [g4].[AssignedCityName], [g4].[CityOfBirthName], [g4].[Discriminator], [g4].[FullName], [g4].[HasSoulPatch], [g4].[LeaderNickname], [g4].[LeaderSquadId], [g4].[Rank]
+ FROM [Gears] AS [g4]
+ ) AS [t0]
+ WHERE [t0].[Nickname] = N'Marcus') AND ([s].[Name] = [s0].[Name] OR ([s].[Name] IS NULL AND [s0].[Name] IS NULL))) AS [SumOfLengths]
+FROM [Gears] AS [g]
+INNER JOIN [Squads] AS [s] ON [g].[SquadId] = [s].[Id]
+WHERE EXISTS (
+ SELECT 1
+ FROM (
+ SELECT [g0].[Nickname], [g0].[SquadId], [g0].[AssignedCityName], [g0].[CityOfBirthName], [g0].[Discriminator], [g0].[FullName], [g0].[HasSoulPatch], [g0].[LeaderNickname], [g0].[LeaderSquadId], [g0].[Rank]
+ FROM [Gears] AS [g0]
+ UNION ALL
+ SELECT [g1].[Nickname], [g1].[SquadId], [g1].[AssignedCityName], [g1].[CityOfBirthName], [g1].[Discriminator], [g1].[FullName], [g1].[HasSoulPatch], [g1].[LeaderNickname], [g1].[LeaderSquadId], [g1].[Rank]
+ FROM [Gears] AS [g1]
+ ) AS [t]
+ WHERE [t].[Nickname] = N'Marcus')
+GROUP BY [s].[Name]
+""");
+ }
+
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 dbd7228bf91..10608526023 100644
--- a/test/EFCore.SqlServer.FunctionalTests/Query/TPCGearsOfWarQuerySqlServerTest.cs
+++ b/test/EFCore.SqlServer.FunctionalTests/Query/TPCGearsOfWarQuerySqlServerTest.cs
@@ -13202,6 +13202,67 @@ FROM [SquadMissions] AS [s0]
""");
}
+ public override async Task Set_operator_with_navigation_in_projection_groupby_aggregate(bool async)
+ {
+ await base.Set_operator_with_navigation_in_projection_groupby_aggregate(async);
+
+ AssertSql(
+"""
+SELECT [s].[Name], (
+ SELECT COALESCE(SUM(CAST(LEN([c].[Location]) AS int)), 0)
+ FROM (
+ SELECT [g0].[Nickname], [g0].[SquadId], [g0].[AssignedCityName], [g0].[CityOfBirthName], [g0].[FullName], [g0].[HasSoulPatch], [g0].[LeaderNickname], [g0].[LeaderSquadId], [g0].[Rank], N'Gear' AS [Discriminator]
+ FROM [Gears] AS [g0]
+ UNION ALL
+ SELECT [o0].[Nickname], [o0].[SquadId], [o0].[AssignedCityName], [o0].[CityOfBirthName], [o0].[FullName], [o0].[HasSoulPatch], [o0].[LeaderNickname], [o0].[LeaderSquadId], [o0].[Rank], N'Officer' AS [Discriminator]
+ FROM [Officers] AS [o0]
+ ) AS [t3]
+ INNER JOIN [Squads] AS [s0] ON [t3].[SquadId] = [s0].[Id]
+ INNER JOIN [Cities] AS [c] ON [t3].[CityOfBirthName] = [c].[Name]
+ WHERE EXISTS (
+ SELECT 1
+ FROM (
+ SELECT [g1].[Nickname], [g1].[SquadId], [g1].[AssignedCityName], [g1].[CityOfBirthName], [g1].[FullName], [g1].[HasSoulPatch], [g1].[LeaderNickname], [g1].[LeaderSquadId], [g1].[Rank], N'Gear' AS [Discriminator]
+ FROM [Gears] AS [g1]
+ UNION ALL
+ SELECT [o1].[Nickname], [o1].[SquadId], [o1].[AssignedCityName], [o1].[CityOfBirthName], [o1].[FullName], [o1].[HasSoulPatch], [o1].[LeaderNickname], [o1].[LeaderSquadId], [o1].[Rank], N'Officer' AS [Discriminator]
+ FROM [Officers] AS [o1]
+ UNION ALL
+ SELECT [g2].[Nickname], [g2].[SquadId], [g2].[AssignedCityName], [g2].[CityOfBirthName], [g2].[FullName], [g2].[HasSoulPatch], [g2].[LeaderNickname], [g2].[LeaderSquadId], [g2].[Rank], N'Gear' AS [Discriminator]
+ FROM [Gears] AS [g2]
+ UNION ALL
+ SELECT [o2].[Nickname], [o2].[SquadId], [o2].[AssignedCityName], [o2].[CityOfBirthName], [o2].[FullName], [o2].[HasSoulPatch], [o2].[LeaderNickname], [o2].[LeaderSquadId], [o2].[Rank], N'Officer' AS [Discriminator]
+ FROM [Officers] AS [o2]
+ ) AS [t4]
+ WHERE [t4].[Nickname] = N'Marcus') AND ([s].[Name] = [s0].[Name] OR ([s].[Name] IS NULL AND [s0].[Name] IS NULL))) AS [SumOfLengths]
+FROM (
+ SELECT [g].[SquadId]
+ FROM [Gears] AS [g]
+ UNION ALL
+ SELECT [o].[SquadId]
+ FROM [Officers] AS [o]
+) AS [t]
+INNER JOIN [Squads] AS [s] ON [t].[SquadId] = [s].[Id]
+WHERE EXISTS (
+ SELECT 1
+ FROM (
+ SELECT [g3].[Nickname], [g3].[SquadId], [g3].[AssignedCityName], [g3].[CityOfBirthName], [g3].[FullName], [g3].[HasSoulPatch], [g3].[LeaderNickname], [g3].[LeaderSquadId], [g3].[Rank], N'Gear' AS [Discriminator]
+ FROM [Gears] AS [g3]
+ UNION ALL
+ SELECT [o3].[Nickname], [o3].[SquadId], [o3].[AssignedCityName], [o3].[CityOfBirthName], [o3].[FullName], [o3].[HasSoulPatch], [o3].[LeaderNickname], [o3].[LeaderSquadId], [o3].[Rank], N'Officer' AS [Discriminator]
+ FROM [Officers] AS [o3]
+ UNION ALL
+ SELECT [g4].[Nickname], [g4].[SquadId], [g4].[AssignedCityName], [g4].[CityOfBirthName], [g4].[FullName], [g4].[HasSoulPatch], [g4].[LeaderNickname], [g4].[LeaderSquadId], [g4].[Rank], N'Gear' AS [Discriminator]
+ FROM [Gears] AS [g4]
+ UNION ALL
+ SELECT [o4].[Nickname], [o4].[SquadId], [o4].[AssignedCityName], [o4].[CityOfBirthName], [o4].[FullName], [o4].[HasSoulPatch], [o4].[LeaderNickname], [o4].[LeaderSquadId], [o4].[Rank], N'Officer' AS [Discriminator]
+ FROM [Officers] AS [o4]
+ ) AS [t0]
+ WHERE [t0].[Nickname] = N'Marcus')
+GROUP BY [s].[Name]
+""");
+ }
+
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 63dfe44a6a1..9584725abb2 100644
--- a/test/EFCore.SqlServer.FunctionalTests/Query/TPTGearsOfWarQuerySqlServerTest.cs
+++ b/test/EFCore.SqlServer.FunctionalTests/Query/TPTGearsOfWarQuerySqlServerTest.cs
@@ -11358,6 +11358,56 @@ FROM [SquadMissions] AS [s0]
""");
}
+ public override async Task Set_operator_with_navigation_in_projection_groupby_aggregate(bool async)
+ {
+ await base.Set_operator_with_navigation_in_projection_groupby_aggregate(async);
+
+ AssertSql(
+"""
+SELECT [s].[Name], (
+ SELECT COALESCE(SUM(CAST(LEN([c].[Location]) AS int)), 0)
+ FROM [Gears] AS [g2]
+ LEFT JOIN [Officers] AS [o2] ON [g2].[Nickname] = [o2].[Nickname] AND [g2].[SquadId] = [o2].[SquadId]
+ INNER JOIN [Squads] AS [s0] ON [g2].[SquadId] = [s0].[Id]
+ INNER JOIN [Cities] AS [c] ON [g2].[CityOfBirthName] = [c].[Name]
+ WHERE EXISTS (
+ SELECT 1
+ FROM (
+ SELECT [g3].[Nickname], [g3].[SquadId], [g3].[AssignedCityName], [g3].[CityOfBirthName], [g3].[FullName], [g3].[HasSoulPatch], [g3].[LeaderNickname], [g3].[LeaderSquadId], [g3].[Rank], CASE
+ WHEN [o3].[Nickname] IS NOT NULL THEN N'Officer'
+ END AS [Discriminator]
+ FROM [Gears] AS [g3]
+ LEFT JOIN [Officers] AS [o3] ON [g3].[Nickname] = [o3].[Nickname] AND [g3].[SquadId] = [o3].[SquadId]
+ UNION ALL
+ SELECT [g4].[Nickname], [g4].[SquadId], [g4].[AssignedCityName], [g4].[CityOfBirthName], [g4].[FullName], [g4].[HasSoulPatch], [g4].[LeaderNickname], [g4].[LeaderSquadId], [g4].[Rank], CASE
+ WHEN [o4].[Nickname] IS NOT NULL THEN N'Officer'
+ END AS [Discriminator]
+ FROM [Gears] AS [g4]
+ LEFT JOIN [Officers] AS [o4] ON [g4].[Nickname] = [o4].[Nickname] AND [g4].[SquadId] = [o4].[SquadId]
+ ) AS [t0]
+ WHERE [t0].[Nickname] = N'Marcus') AND ([s].[Name] = [s0].[Name] OR ([s].[Name] IS NULL AND [s0].[Name] IS NULL))) AS [SumOfLengths]
+FROM [Gears] AS [g]
+INNER JOIN [Squads] AS [s] ON [g].[SquadId] = [s].[Id]
+WHERE EXISTS (
+ SELECT 1
+ FROM (
+ SELECT [g0].[Nickname], [g0].[SquadId], [g0].[AssignedCityName], [g0].[CityOfBirthName], [g0].[FullName], [g0].[HasSoulPatch], [g0].[LeaderNickname], [g0].[LeaderSquadId], [g0].[Rank], CASE
+ WHEN [o0].[Nickname] IS NOT NULL THEN N'Officer'
+ END AS [Discriminator]
+ FROM [Gears] AS [g0]
+ LEFT JOIN [Officers] AS [o0] ON [g0].[Nickname] = [o0].[Nickname] AND [g0].[SquadId] = [o0].[SquadId]
+ UNION ALL
+ SELECT [g1].[Nickname], [g1].[SquadId], [g1].[AssignedCityName], [g1].[CityOfBirthName], [g1].[FullName], [g1].[HasSoulPatch], [g1].[LeaderNickname], [g1].[LeaderSquadId], [g1].[Rank], CASE
+ WHEN [o1].[Nickname] IS NOT NULL THEN N'Officer'
+ END AS [Discriminator]
+ FROM [Gears] AS [g1]
+ LEFT JOIN [Officers] AS [o1] ON [g1].[Nickname] = [o1].[Nickname] AND [g1].[SquadId] = [o1].[SquadId]
+ ) AS [t]
+ WHERE [t].[Nickname] = N'Marcus')
+GROUP BY [s].[Name]
+""");
+ }
+
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 63b8a51dac9..2ec49776d41 100644
--- a/test/EFCore.SqlServer.FunctionalTests/Query/TemporalGearsOfWarQuerySqlServerTest.cs
+++ b/test/EFCore.SqlServer.FunctionalTests/Query/TemporalGearsOfWarQuerySqlServerTest.cs
@@ -9926,6 +9926,43 @@ SELECT 1
""");
}
+ public override async Task Set_operator_with_navigation_in_projection_groupby_aggregate(bool async)
+ {
+ await base.Set_operator_with_navigation_in_projection_groupby_aggregate(async);
+
+ AssertSql(
+"""
+SELECT [s].[Name], (
+ SELECT COALESCE(SUM(CAST(LEN([c].[Location]) AS int)), 0)
+ FROM [Gears] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [g2]
+ INNER JOIN [Squads] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [s0] ON [g2].[SquadId] = [s0].[Id]
+ INNER JOIN [Cities] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [c] ON [g2].[CityOfBirthName] = [c].[Name]
+ WHERE EXISTS (
+ SELECT 1
+ FROM (
+ SELECT [g3].[Nickname], [g3].[SquadId], [g3].[AssignedCityName], [g3].[CityOfBirthName], [g3].[Discriminator], [g3].[FullName], [g3].[HasSoulPatch], [g3].[LeaderNickname], [g3].[LeaderSquadId], [g3].[PeriodEnd], [g3].[PeriodStart], [g3].[Rank]
+ FROM [Gears] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [g3]
+ UNION ALL
+ SELECT [g4].[Nickname], [g4].[SquadId], [g4].[AssignedCityName], [g4].[CityOfBirthName], [g4].[Discriminator], [g4].[FullName], [g4].[HasSoulPatch], [g4].[LeaderNickname], [g4].[LeaderSquadId], [g4].[PeriodEnd], [g4].[PeriodStart], [g4].[Rank]
+ FROM [Gears] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [g4]
+ ) AS [t0]
+ WHERE [t0].[Nickname] = N'Marcus') AND ([s].[Name] = [s0].[Name] OR ([s].[Name] IS NULL AND [s0].[Name] IS NULL))) AS [SumOfLengths]
+FROM [Gears] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [g]
+INNER JOIN [Squads] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [s] ON [g].[SquadId] = [s].[Id]
+WHERE EXISTS (
+ SELECT 1
+ FROM (
+ SELECT [g0].[Nickname], [g0].[SquadId], [g0].[AssignedCityName], [g0].[CityOfBirthName], [g0].[Discriminator], [g0].[FullName], [g0].[HasSoulPatch], [g0].[LeaderNickname], [g0].[LeaderSquadId], [g0].[PeriodEnd], [g0].[PeriodStart], [g0].[Rank]
+ FROM [Gears] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [g0]
+ UNION ALL
+ SELECT [g1].[Nickname], [g1].[SquadId], [g1].[AssignedCityName], [g1].[CityOfBirthName], [g1].[Discriminator], [g1].[FullName], [g1].[HasSoulPatch], [g1].[LeaderNickname], [g1].[LeaderSquadId], [g1].[PeriodEnd], [g1].[PeriodStart], [g1].[Rank]
+ FROM [Gears] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [g1]
+ ) AS [t]
+ WHERE [t].[Nickname] = N'Marcus')
+GROUP BY [s].[Name]
+""");
+ }
+
private void AssertSql(params string[] expected)
=> Fixture.TestSqlLoggerFactory.AssertBaseline(expected);
}
diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/UdfDbFunctionSqlServerTests.cs b/test/EFCore.SqlServer.FunctionalTests/Query/UdfDbFunctionSqlServerTests.cs
index f06505b1baf..3113d18a51f 100644
--- a/test/EFCore.SqlServer.FunctionalTests/Query/UdfDbFunctionSqlServerTests.cs
+++ b/test/EFCore.SqlServer.FunctionalTests/Query/UdfDbFunctionSqlServerTests.cs
@@ -899,6 +899,62 @@ ORDER BY [g].[ProductId]
""");
}
+ public override void TVF_with_navigation_in_projection_groupby_aggregate()
+ {
+ base.TVF_with_navigation_in_projection_groupby_aggregate();
+
+ AssertSql(
+"""
+SELECT [c].[LastName], (
+ SELECT COALESCE(SUM(CAST(LEN([c1].[FirstName]) AS int)), 0)
+ FROM [Orders] AS [o0]
+ INNER JOIN [Customers] AS [c0] ON [o0].[CustomerId] = [c0].[Id]
+ INNER JOIN [Customers] AS [c1] ON [o0].[CustomerId] = [c1].[Id]
+ WHERE NOT (EXISTS (
+ SELECT 1
+ FROM [dbo].[GetTopTwoSellingProducts]() AS [g0]
+ WHERE [g0].[ProductId] = 25)) AND ([c].[LastName] = [c0].[LastName] OR ([c].[LastName] IS NULL AND [c0].[LastName] IS NULL))) AS [SumOfLengths]
+FROM [Orders] AS [o]
+INNER JOIN [Customers] AS [c] ON [o].[CustomerId] = [c].[Id]
+WHERE NOT (EXISTS (
+ SELECT 1
+ FROM [dbo].[GetTopTwoSellingProducts]() AS [g]
+ WHERE [g].[ProductId] = 25))
+GROUP BY [c].[LastName]
+""");
+ }
+
+ public override void TVF_with_argument_being_a_subquery_with_navigation_in_projection_groupby_aggregate()
+ {
+ base.TVF_with_argument_being_a_subquery_with_navigation_in_projection_groupby_aggregate();
+
+ AssertSql(
+"""
+SELECT [c0].[LastName], (
+ SELECT COALESCE(SUM(CAST(LEN([c2].[FirstName]) AS int)), 0)
+ FROM [Orders] AS [o0]
+ INNER JOIN [Customers] AS [c1] ON [o0].[CustomerId] = [c1].[Id]
+ INNER JOIN [Customers] AS [c2] ON [o0].[CustomerId] = [c2].[Id]
+ WHERE NOT (EXISTS (
+ SELECT 1
+ FROM [dbo].[GetOrdersWithMultipleProducts]((
+ SELECT TOP(1) [c3].[Id]
+ FROM [Customers] AS [c3]
+ ORDER BY [c3].[Id])) AS [g0]
+ WHERE [g0].[CustomerId] = 25)) AND ([c0].[LastName] = [c1].[LastName] OR ([c0].[LastName] IS NULL AND [c1].[LastName] IS NULL))) AS [SumOfLengths]
+FROM [Orders] AS [o]
+INNER JOIN [Customers] AS [c0] ON [o].[CustomerId] = [c0].[Id]
+WHERE NOT (EXISTS (
+ SELECT 1
+ FROM [dbo].[GetOrdersWithMultipleProducts]((
+ SELECT TOP(1) [c].[Id]
+ FROM [Customers] AS [c]
+ ORDER BY [c].[Id])) AS [g]
+ WHERE [g].[CustomerId] = 25))
+GROUP BY [c0].[LastName]
+""");
+ }
+
public override void TVF_backing_entity_type_mapped_to_view()
{
base.TVF_backing_entity_type_mapped_to_view();
diff --git a/test/EFCore.Sqlite.FunctionalTests/Query/GearsOfWarQuerySqliteTest.cs b/test/EFCore.Sqlite.FunctionalTests/Query/GearsOfWarQuerySqliteTest.cs
index cd80fa08b4b..268b4a5c4da 100644
--- a/test/EFCore.Sqlite.FunctionalTests/Query/GearsOfWarQuerySqliteTest.cs
+++ b/test/EFCore.Sqlite.FunctionalTests/Query/GearsOfWarQuerySqliteTest.cs
@@ -9396,6 +9396,43 @@ public override async Task Using_indexer_on_byte_array_and_string_in_projection(
""");
}
+ public override async Task Set_operator_with_navigation_in_projection_groupby_aggregate(bool async)
+ {
+ await base.Set_operator_with_navigation_in_projection_groupby_aggregate(async);
+
+ AssertSql(
+"""
+SELECT "s"."Name", (
+ SELECT COALESCE(SUM(length("c"."Location")), 0)
+ FROM "Gears" AS "g2"
+ INNER JOIN "Squads" AS "s0" ON "g2"."SquadId" = "s0"."Id"
+ INNER JOIN "Cities" AS "c" ON "g2"."CityOfBirthName" = "c"."Name"
+ WHERE EXISTS (
+ SELECT 1
+ FROM (
+ SELECT "g3"."Nickname", "g3"."SquadId", "g3"."AssignedCityName", "g3"."CityOfBirthName", "g3"."Discriminator", "g3"."FullName", "g3"."HasSoulPatch", "g3"."LeaderNickname", "g3"."LeaderSquadId", "g3"."Rank"
+ FROM "Gears" AS "g3"
+ UNION ALL
+ SELECT "g4"."Nickname", "g4"."SquadId", "g4"."AssignedCityName", "g4"."CityOfBirthName", "g4"."Discriminator", "g4"."FullName", "g4"."HasSoulPatch", "g4"."LeaderNickname", "g4"."LeaderSquadId", "g4"."Rank"
+ FROM "Gears" AS "g4"
+ ) AS "t0"
+ WHERE "t0"."Nickname" = 'Marcus') AND ("s"."Name" = "s0"."Name" OR ("s"."Name" IS NULL AND "s0"."Name" IS NULL))) AS "SumOfLengths"
+FROM "Gears" AS "g"
+INNER JOIN "Squads" AS "s" ON "g"."SquadId" = "s"."Id"
+WHERE EXISTS (
+ SELECT 1
+ FROM (
+ SELECT "g0"."Nickname", "g0"."SquadId", "g0"."AssignedCityName", "g0"."CityOfBirthName", "g0"."Discriminator", "g0"."FullName", "g0"."HasSoulPatch", "g0"."LeaderNickname", "g0"."LeaderSquadId", "g0"."Rank"
+ FROM "Gears" AS "g0"
+ UNION ALL
+ SELECT "g1"."Nickname", "g1"."SquadId", "g1"."AssignedCityName", "g1"."CityOfBirthName", "g1"."Discriminator", "g1"."FullName", "g1"."HasSoulPatch", "g1"."LeaderNickname", "g1"."LeaderSquadId", "g1"."Rank"
+ FROM "Gears" AS "g1"
+ ) AS "t"
+ WHERE "t"."Nickname" = 'Marcus')
+GROUP BY "s"."Name"
+""");
+ }
+
public override Task DateTimeOffset_to_unix_time_milliseconds(bool async)
=> AssertTranslationFailed(() => base.DateTimeOffset_to_unix_time_milliseconds(async));