diff --git a/src/EFCore.Relational/Query/RelationalQueryableMethodTranslatingExpressionVisitor.cs b/src/EFCore.Relational/Query/RelationalQueryableMethodTranslatingExpressionVisitor.cs
index 79436458c77..047e3cf004a 100644
--- a/src/EFCore.Relational/Query/RelationalQueryableMethodTranslatingExpressionVisitor.cs
+++ b/src/EFCore.Relational/Query/RelationalQueryableMethodTranslatingExpressionVisitor.cs
@@ -473,7 +473,7 @@ private static ShapedQueryExpression CreateShapedQueryExpression(IEntityType ent
source = TranslateSelect(source, elementSelector);
}
- selectExpression.ApplyGrouping(translatedKey);
+ translatedKey = selectExpression.ApplyGrouping(translatedKey);
var groupByShaper = new GroupByShaperExpression(translatedKey, source.ShaperExpression);
if (resultSelector == null)
diff --git a/src/EFCore.Relational/Query/SqlExpressions/SelectExpression.cs b/src/EFCore.Relational/Query/SqlExpressions/SelectExpression.cs
index ceb7e85dd1f..f9053ff69b5 100644
--- a/src/EFCore.Relational/Query/SqlExpressions/SelectExpression.cs
+++ b/src/EFCore.Relational/Query/SqlExpressions/SelectExpression.cs
@@ -1062,13 +1062,34 @@ public void ApplyPredicate(SqlExpression expression)
/// Applies grouping from given key selector.
///
/// An key selector expression for the GROUP BY.
- public void ApplyGrouping(Expression keySelector)
+ public Expression ApplyGrouping(Expression keySelector)
{
Check.NotNull(keySelector, nameof(keySelector));
ClearOrdering();
- AppendGroupBy(keySelector);
+ var groupByTerms = new List();
+ var groupByAliases = new List();
+ AppendGroupBy(keySelector, groupByTerms, groupByAliases, "Key");
+
+ if (groupByTerms.Any(e => e is SqlConstantExpression || e is SqlParameterExpression || e is ScalarSubqueryExpression))
+ {
+ var sqlRemappingVisitor = PushdownIntoSubqueryInternal();
+ var newGroupByTerms = new List(groupByTerms.Count);
+ var subquery = (SelectExpression)_tables[0];
+ var subqueryTableReference = _tableReferences[0];
+ for (var i = 0; i < groupByTerms.Count; i++)
+ {
+ var item = groupByTerms[i];
+ var newItem = subquery._projection.Any(e => e.Expression.Equals(item))
+ ? sqlRemappingVisitor.Remap(item)
+ : subquery.GenerateOuterColumn(subqueryTableReference, item, groupByAliases[i] ?? "Key");
+ newGroupByTerms.Add(newItem);
+ }
+ keySelector = new ReplacingExpressionVisitor(groupByTerms, newGroupByTerms).Visit(keySelector);
+ groupByTerms = newGroupByTerms;
+ }
+ _groupBy.AddRange(groupByTerms);
if (!_identifier.All(e => _groupBy.Contains(e.Column)))
{
@@ -1078,44 +1099,41 @@ public void ApplyGrouping(Expression keySelector)
_identifier.AddRange(_groupBy.Select(e => ((ColumnExpression)e, e.TypeMapping!.KeyComparer)));
}
}
+
+ return keySelector;
}
- private void AppendGroupBy(Expression keySelector)
+ private void AppendGroupBy(Expression keySelector, List groupByTerms, List groupByAliases, string? name)
{
Check.NotNull(keySelector, nameof(keySelector));
switch (keySelector)
{
case SqlExpression sqlExpression:
- if (!(sqlExpression is SqlConstantExpression
- || sqlExpression is SqlParameterExpression))
- {
- _groupBy.Add(sqlExpression);
- }
-
+ groupByTerms.Add(sqlExpression);
+ groupByAliases.Add(name);
break;
case NewExpression newExpression:
- foreach (var argument in newExpression.Arguments)
+ for (var i = 0; i < newExpression.Arguments.Count; i++)
{
- AppendGroupBy(argument);
+ AppendGroupBy(newExpression.Arguments[i], groupByTerms, groupByAliases, newExpression.Members?[i].Name);
}
-
break;
case MemberInitExpression memberInitExpression:
- AppendGroupBy(memberInitExpression.NewExpression);
+ AppendGroupBy(memberInitExpression.NewExpression, groupByTerms, groupByAliases, null);
foreach (var argument in memberInitExpression.Bindings)
{
- AppendGroupBy(((MemberAssignment)argument).Expression);
+ var memberAssignment = (MemberAssignment)argument;
+ AppendGroupBy(memberAssignment.Expression, groupByTerms, groupByAliases, memberAssignment.Member.Name);
}
-
break;
case UnaryExpression unaryExpression
when unaryExpression.NodeType == ExpressionType.Convert
|| unaryExpression.NodeType == ExpressionType.ConvertChecked:
- AppendGroupBy(unaryExpression.Operand);
+ AppendGroupBy(unaryExpression.Operand, groupByTerms, groupByAliases, name);
break;
default:
diff --git a/test/EFCore.Specification.Tests/Query/NorthwindGroupByQueryTestBase.cs b/test/EFCore.Specification.Tests/Query/NorthwindGroupByQueryTestBase.cs
index c8c4293741c..38f1fd26198 100644
--- a/test/EFCore.Specification.Tests/Query/NorthwindGroupByQueryTestBase.cs
+++ b/test/EFCore.Specification.Tests/Query/NorthwindGroupByQueryTestBase.cs
@@ -959,7 +959,8 @@ public virtual Task GroupBy_constant_with_where_on_grouping_with_aggregate_opera
ss => ss.Set().GroupBy(o => 1)
.OrderBy(g => g.Key)
.Select(
- g => new {
+ g => new
+ {
Min = g.Where(i => 1 == g.Key).Min(o => o.OrderDate),
Max = g.Where(i => 1 == g.Key).Max(o => o.OrderDate),
Sum = g.Where(i => 1 == g.Key).Sum(o => o.OrderID),
@@ -2917,6 +2918,27 @@ public virtual Task All_with_predicate_after_GroupBy_without_aggregate(bool asyn
g => g.Count() > 1);
}
+ [ConditionalTheory]
+ [MemberData(nameof(IsAsyncData))]
+ public virtual Task GroupBy_aggregate_followed_by_another_GroupBy_aggregate(bool async)
+ {
+ return AssertQuery(
+ async,
+ ss => ss.Set().GroupBy(o => o.CustomerID)
+ .Select(g => new
+ {
+ g.Key,
+ Count = g.Count(),
+ LastOrder = g.Max(e => e.OrderID)
+ })
+ .GroupBy(e => 1)
+ .Select(g => new
+ {
+ g.Key,
+ Count = g.Sum(e => e.Count)
+ }));
+ }
+
#endregion
# region GroupByInSubquery
diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindGroupByQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindGroupByQuerySqlServerTest.cs
index 20d66927a8d..e7a943eb174 100644
--- a/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindGroupByQuerySqlServerTest.cs
+++ b/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindGroupByQuerySqlServerTest.cs
@@ -203,9 +203,12 @@ public override async Task GroupBy_Property_Select_Key_with_constant(bool async)
await base.GroupBy_Property_Select_Key_with_constant(async);
AssertSql(
- @"SELECT N'CustomerID' AS [Name], [o].[CustomerID] AS [Value], COUNT(*) AS [Count]
-FROM [Orders] AS [o]
-GROUP BY [o].[CustomerID]");
+ @"SELECT [t].[Name], [t].[CustomerID] AS [Value], COUNT(*) AS [Count]
+FROM (
+ SELECT [o].[CustomerID], N'CustomerID' AS [Name]
+ FROM [Orders] AS [o]
+) AS [t]
+GROUP BY [t].[Name], [t].[CustomerID]");
}
public override async Task GroupBy_aggregate_projecting_conditional_expression(bool async)
@@ -522,8 +525,12 @@ public override async Task GroupBy_Constant_Select_Sum_Min_Key_Max_Avg(bool asyn
await base.GroupBy_Constant_Select_Sum_Min_Key_Max_Avg(async);
AssertSql(
- @"SELECT COALESCE(SUM([o].[OrderID]), 0) AS [Sum], MIN([o].[OrderID]) AS [Min], 2 AS [Key], MAX([o].[OrderID]) AS [Max], AVG(CAST([o].[OrderID] AS float)) AS [Avg]
-FROM [Orders] AS [o]");
+ @"SELECT COALESCE(SUM([t].[OrderID]), 0) AS [Sum], MIN([t].[OrderID]) AS [Min], [t].[Key], MAX([t].[OrderID]) AS [Max], AVG(CAST([t].[OrderID] AS float)) AS [Avg]
+FROM (
+ SELECT [o].[OrderID], 2 AS [Key]
+ FROM [Orders] AS [o]
+) AS [t]
+GROUP BY [t].[Key]");
}
public override async Task GroupBy_Constant_with_element_selector_Select_Sum(bool async)
@@ -531,8 +538,12 @@ public override async Task GroupBy_Constant_with_element_selector_Select_Sum(boo
await base.GroupBy_Constant_with_element_selector_Select_Sum(async);
AssertSql(
- @"SELECT COALESCE(SUM([o].[OrderID]), 0) AS [Sum]
-FROM [Orders] AS [o]");
+ @"SELECT COALESCE(SUM([t].[OrderID]), 0) AS [Sum]
+FROM (
+ SELECT [o].[OrderID], 2 AS [Key]
+ FROM [Orders] AS [o]
+) AS [t]
+GROUP BY [t].[Key]");
}
public override async Task GroupBy_Constant_with_element_selector_Select_Sum2(bool async)
@@ -540,8 +551,12 @@ public override async Task GroupBy_Constant_with_element_selector_Select_Sum2(bo
await base.GroupBy_Constant_with_element_selector_Select_Sum2(async);
AssertSql(
- @"SELECT COALESCE(SUM([o].[OrderID]), 0) AS [Sum]
-FROM [Orders] AS [o]");
+ @"SELECT COALESCE(SUM([t].[OrderID]), 0) AS [Sum]
+FROM (
+ SELECT [o].[OrderID], 2 AS [Key]
+ FROM [Orders] AS [o]
+) AS [t]
+GROUP BY [t].[Key]");
}
public override async Task GroupBy_Constant_with_element_selector_Select_Sum3(bool async)
@@ -549,8 +564,12 @@ public override async Task GroupBy_Constant_with_element_selector_Select_Sum3(bo
await base.GroupBy_Constant_with_element_selector_Select_Sum3(async);
AssertSql(
- @"SELECT COALESCE(SUM([o].[OrderID]), 0) AS [Sum]
-FROM [Orders] AS [o]");
+ @"SELECT COALESCE(SUM([t].[OrderID]), 0) AS [Sum]
+FROM (
+ SELECT [o].[OrderID], 2 AS [Key]
+ FROM [Orders] AS [o]
+) AS [t]
+GROUP BY [t].[Key]");
}
public override async Task GroupBy_after_predicate_Constant_Select_Sum_Min_Key_Max_Avg(bool async)
@@ -558,9 +577,13 @@ public override async Task GroupBy_after_predicate_Constant_Select_Sum_Min_Key_M
await base.GroupBy_after_predicate_Constant_Select_Sum_Min_Key_Max_Avg(async);
AssertSql(
- @"SELECT COALESCE(SUM([o].[OrderID]), 0) AS [Sum], MIN([o].[OrderID]) AS [Min], 2 AS [Random], MAX([o].[OrderID]) AS [Max], AVG(CAST([o].[OrderID] AS float)) AS [Avg]
-FROM [Orders] AS [o]
-WHERE [o].[OrderID] > 10500");
+ @"SELECT COALESCE(SUM([t].[OrderID]), 0) AS [Sum], MIN([t].[OrderID]) AS [Min], [t].[Key] AS [Random], MAX([t].[OrderID]) AS [Max], AVG(CAST([t].[OrderID] AS float)) AS [Avg]
+FROM (
+ SELECT [o].[OrderID], 2 AS [Key]
+ FROM [Orders] AS [o]
+ WHERE [o].[OrderID] > 10500
+) AS [t]
+GROUP BY [t].[Key]");
}
public override async Task GroupBy_Constant_with_element_selector_Select_Sum_Min_Key_Max_Avg(bool async)
@@ -568,8 +591,12 @@ public override async Task GroupBy_Constant_with_element_selector_Select_Sum_Min
await base.GroupBy_Constant_with_element_selector_Select_Sum_Min_Key_Max_Avg(async);
AssertSql(
- @"SELECT COALESCE(SUM([o].[OrderID]), 0) AS [Sum], 2 AS [Key]
-FROM [Orders] AS [o]");
+ @"SELECT COALESCE(SUM([t].[OrderID]), 0) AS [Sum], [t].[Key]
+FROM (
+ SELECT [o].[OrderID], 2 AS [Key]
+ FROM [Orders] AS [o]
+) AS [t]
+GROUP BY [t].[Key]");
}
public override async Task GroupBy_constant_with_where_on_grouping_with_aggregate_operators(bool async)
@@ -577,8 +604,21 @@ public override async Task GroupBy_constant_with_where_on_grouping_with_aggregat
await base.GroupBy_constant_with_where_on_grouping_with_aggregate_operators(async);
AssertSql(
- @"SELECT MIN([o].[OrderDate]) AS [Min], MAX([o].[OrderDate]) AS [Max], COALESCE(SUM([o].[OrderID]), 0) AS [Sum], AVG(CAST([o].[OrderID] AS float)) AS [Average]
-FROM [Orders] AS [o]");
+ @"SELECT MIN(CASE
+ WHEN 1 = [t].[Key] THEN [t].[OrderDate]
+END) AS [Min], MAX(CASE
+ WHEN 1 = [t].[Key] THEN [t].[OrderDate]
+END) AS [Max], COALESCE(SUM(CASE
+ WHEN 1 = [t].[Key] THEN [t].[OrderID]
+END), 0) AS [Sum], AVG(CAST(CASE
+ WHEN 1 = [t].[Key] THEN [t].[OrderID]
+END AS float)) AS [Average]
+FROM (
+ SELECT [o].[OrderID], [o].[OrderDate], 1 AS [Key]
+ FROM [Orders] AS [o]
+) AS [t]
+GROUP BY [t].[Key]
+ORDER BY [t].[Key]");
}
public override async Task GroupBy_param_Select_Sum_Min_Key_Max_Avg(bool async)
@@ -588,8 +628,12 @@ public override async Task GroupBy_param_Select_Sum_Min_Key_Max_Avg(bool async)
AssertSql(
@"@__a_0='2'
-SELECT COALESCE(SUM([o].[OrderID]), 0) AS [Sum], MIN([o].[OrderID]) AS [Min], @__a_0 AS [Key], MAX([o].[OrderID]) AS [Max], AVG(CAST([o].[OrderID] AS float)) AS [Avg]
-FROM [Orders] AS [o]");
+SELECT COALESCE(SUM([t].[OrderID]), 0) AS [Sum], MIN([t].[OrderID]) AS [Min], [t].[Key], MAX([t].[OrderID]) AS [Max], AVG(CAST([t].[OrderID] AS float)) AS [Avg]
+FROM (
+ SELECT [o].[OrderID], @__a_0 AS [Key]
+ FROM [Orders] AS [o]
+) AS [t]
+GROUP BY [t].[Key]");
}
public override async Task GroupBy_param_with_element_selector_Select_Sum(bool async)
@@ -597,8 +641,14 @@ public override async Task GroupBy_param_with_element_selector_Select_Sum(bool a
await base.GroupBy_param_with_element_selector_Select_Sum(async);
AssertSql(
- @"SELECT COALESCE(SUM([o].[OrderID]), 0) AS [Sum]
-FROM [Orders] AS [o]");
+ @"@__a_0='2'
+
+SELECT COALESCE(SUM([t].[OrderID]), 0) AS [Sum]
+FROM (
+ SELECT [o].[OrderID], @__a_0 AS [Key]
+ FROM [Orders] AS [o]
+) AS [t]
+GROUP BY [t].[Key]");
}
public override async Task GroupBy_param_with_element_selector_Select_Sum2(bool async)
@@ -606,8 +656,14 @@ public override async Task GroupBy_param_with_element_selector_Select_Sum2(bool
await base.GroupBy_param_with_element_selector_Select_Sum2(async);
AssertSql(
- @"SELECT COALESCE(SUM([o].[OrderID]), 0) AS [Sum]
-FROM [Orders] AS [o]");
+ @"@__a_0='2'
+
+SELECT COALESCE(SUM([t].[OrderID]), 0) AS [Sum]
+FROM (
+ SELECT [o].[OrderID], @__a_0 AS [Key]
+ FROM [Orders] AS [o]
+) AS [t]
+GROUP BY [t].[Key]");
}
public override async Task GroupBy_param_with_element_selector_Select_Sum3(bool async)
@@ -615,8 +671,14 @@ public override async Task GroupBy_param_with_element_selector_Select_Sum3(bool
await base.GroupBy_param_with_element_selector_Select_Sum3(async);
AssertSql(
- @"SELECT COALESCE(SUM([o].[OrderID]), 0) AS [Sum]
-FROM [Orders] AS [o]");
+ @"@__a_0='2'
+
+SELECT COALESCE(SUM([t].[OrderID]), 0) AS [Sum]
+FROM (
+ SELECT [o].[OrderID], @__a_0 AS [Key]
+ FROM [Orders] AS [o]
+) AS [t]
+GROUP BY [t].[Key]");
}
public override async Task GroupBy_param_with_element_selector_Select_Sum_Min_Key_Max_Avg(bool async)
@@ -626,8 +688,12 @@ public override async Task GroupBy_param_with_element_selector_Select_Sum_Min_Ke
AssertSql(
@"@__a_0='2'
-SELECT COALESCE(SUM([o].[OrderID]), 0) AS [Sum], @__a_0 AS [Key]
-FROM [Orders] AS [o]");
+SELECT COALESCE(SUM([t].[OrderID]), 0) AS [Sum], [t].[Key]
+FROM (
+ SELECT [o].[OrderID], @__a_0 AS [Key]
+ FROM [Orders] AS [o]
+) AS [t]
+GROUP BY [t].[Key]");
}
public override async Task GroupBy_anonymous_key_type_mismatch_with_aggregate(bool async)
@@ -2085,6 +2151,23 @@ ELSE CAST(0 AS bit)
END");
}
+ public override async Task GroupBy_aggregate_followed_by_another_GroupBy_aggregate(bool async)
+ {
+ await base.GroupBy_aggregate_followed_by_another_GroupBy_aggregate(async);
+
+ AssertSql(
+ @"SELECT [t0].[Key0] AS [Key], COALESCE(SUM([t0].[Count]), 0) AS [Count]
+FROM (
+ SELECT [t].[Count], 1 AS [Key0]
+ FROM (
+ SELECT COUNT(*) AS [Count]
+ FROM [Orders] AS [o]
+ GROUP BY [o].[CustomerID]
+ ) AS [t]
+) AS [t0]
+GROUP BY [t0].[Key0]");
+ }
+
public override async Task GroupBy_based_on_renamed_property_simple(bool async)
{
await base.GroupBy_based_on_renamed_property_simple(async);
@@ -2495,12 +2578,20 @@ FROM [Orders] AS [o]
GROUP BY [o].[CustomerID]");
}
- [ConditionalTheory(Skip = "Issue#19027")]
public override async Task GroupBy_scalar_subquery(bool async)
{
await base.GroupBy_scalar_subquery(async);
- AssertSql(" ");
+ AssertSql(
+ @"SELECT [t].[Key], COUNT(*) AS [Count]
+FROM (
+ SELECT (
+ SELECT TOP(1) [c].[ContactName]
+ FROM [Customers] AS [c]
+ WHERE [c].[CustomerID] = [o].[CustomerID]) AS [Key]
+ FROM [Orders] AS [o]
+) AS [t]
+GROUP BY [t].[Key]");
}
public override async Task GroupBy_scalar_aggregate_in_set_operation(bool async)
diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/OwnedQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/OwnedQuerySqlServerTest.cs
index fcbccef66a4..1af9f88b1dc 100644
--- a/test/EFCore.SqlServer.FunctionalTests/Query/OwnedQuerySqlServerTest.cs
+++ b/test/EFCore.SqlServer.FunctionalTests/Query/OwnedQuerySqlServerTest.cs
@@ -1079,10 +1079,14 @@ public override async Task GroupBy_with_multiple_aggregates_on_owned_navigation_
await base.GroupBy_with_multiple_aggregates_on_owned_navigation_properties(async);
AssertSql(
- @"SELECT AVG(CAST([s].[Id] AS float)) AS [p1], COALESCE(SUM([s].[Id]), 0) AS [p2], MAX(CAST(LEN([s].[Name]) AS int)) AS [p3]
-FROM [OwnedPerson] AS [o]
-LEFT JOIN [Planet] AS [p] ON [o].[PersonAddress_Country_PlanetId] = [p].[Id]
-LEFT JOIN [Star] AS [s] ON [p].[StarId] = [s].[Id]");
+ @"SELECT AVG(CAST([t].[Id] AS float)) AS [p1], COALESCE(SUM([t].[Id]), 0) AS [p2], MAX(CAST(LEN([t].[Name]) AS int)) AS [p3]
+FROM (
+ SELECT [s].[Id], [s].[Name], 1 AS [Key]
+ FROM [OwnedPerson] AS [o]
+ LEFT JOIN [Planet] AS [p] ON [o].[PersonAddress_Country_PlanetId] = [p].[Id]
+ LEFT JOIN [Star] AS [s] ON [p].[StarId] = [s].[Id]
+) AS [t]
+GROUP BY [t].[Key]");
}
public override async Task Ordering_by_identifying_projection(bool async)