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)