diff --git a/src/EFCore.Relational/Query/RelationalQueryableMethodTranslatingExpressionVisitor.cs b/src/EFCore.Relational/Query/RelationalQueryableMethodTranslatingExpressionVisitor.cs index 66fbb112299..285ebb51052 100644 --- a/src/EFCore.Relational/Query/RelationalQueryableMethodTranslatingExpressionVisitor.cs +++ b/src/EFCore.Relational/Query/RelationalQueryableMethodTranslatingExpressionVisitor.cs @@ -301,8 +301,7 @@ protected override ShapedQueryExpression TranslateGroupBy( var remappedKeySelector = RemapLambdaBody(source, keySelector); - var translatedKey = TranslateGroupingKey(remappedKeySelector) - ?? (remappedKeySelector as ConstantExpression); + var translatedKey = TranslateGroupingKey(remappedKeySelector); if (translatedKey != null) { if (elementSelector != null) @@ -310,13 +309,7 @@ protected override ShapedQueryExpression TranslateGroupBy( source = TranslateSelect(source, elementSelector); } - var sqlKeySelector = translatedKey is ConstantExpression - ? _sqlExpressionFactory.ApplyDefaultTypeMapping(_sqlExpressionFactory.Constant(1)) - : translatedKey; - - var appliedKeySelector = selectExpression.ApplyGrouping(sqlKeySelector); - translatedKey = translatedKey is ConstantExpression ? translatedKey : appliedKeySelector; - + selectExpression.ApplyGrouping(translatedKey); source.ShaperExpression = new GroupByShaperExpression(translatedKey, source.ShaperExpression); if (resultSelector == null) @@ -349,51 +342,54 @@ protected override ShapedQueryExpression TranslateGroupBy( private Expression TranslateGroupingKey(Expression expression) { - if (expression is NewExpression newExpression) + switch (expression) { - if (newExpression.Arguments.Count == 0) - { - return newExpression; - } + case NewExpression newExpression: + if (newExpression.Arguments.Count == 0) + { + return newExpression; + } - var newArguments = new Expression[newExpression.Arguments.Count]; - for (var i = 0; i < newArguments.Length; i++) - { - newArguments[i] = TranslateGroupingKey(newExpression.Arguments[i]); - if (newArguments[i] == null) + var newArguments = new Expression[newExpression.Arguments.Count]; + for (var i = 0; i < newArguments.Length; i++) { - return null; + newArguments[i] = TranslateGroupingKey(newExpression.Arguments[i]); + if (newArguments[i] == null) + { + return null; + } } - } - return newExpression.Update(newArguments); - } + return newExpression.Update(newArguments); - if (expression is MemberInitExpression memberInitExpression) - { - var updatedNewExpression = (NewExpression)TranslateGroupingKey(memberInitExpression.NewExpression); - if (updatedNewExpression == null) - { - return null; - } - - var newBindings = new MemberAssignment[memberInitExpression.Bindings.Count]; - for (var i = 0; i < newBindings.Length; i++) - { - var memberAssignment = (MemberAssignment)memberInitExpression.Bindings[i]; - var visitedExpression = TranslateGroupingKey(memberAssignment.Expression); - if (visitedExpression == null) + case MemberInitExpression memberInitExpression: + var updatedNewExpression = (NewExpression)TranslateGroupingKey(memberInitExpression.NewExpression); + if (updatedNewExpression == null) { return null; } - newBindings[i] = memberAssignment.Update(visitedExpression); - } + var newBindings = new MemberAssignment[memberInitExpression.Bindings.Count]; + for (var i = 0; i < newBindings.Length; i++) + { + var memberAssignment = (MemberAssignment)memberInitExpression.Bindings[i]; + var visitedExpression = TranslateGroupingKey(memberAssignment.Expression); + if (visitedExpression == null) + { + return null; + } - return memberInitExpression.Update(updatedNewExpression, newBindings); - } + newBindings[i] = memberAssignment.Update(visitedExpression); + } - return _sqlTranslator.Translate(expression); + return memberInitExpression.Update(updatedNewExpression, newBindings); + + default: + var translation = _sqlTranslator.Translate(expression); + return translation.Type == expression.Type + ? (Expression)translation + : Expression.Convert(translation, expression.Type); + } } protected override ShapedQueryExpression TranslateGroupJoin(ShapedQueryExpression outer, ShapedQueryExpression inner, LambdaExpression outerKeySelector, LambdaExpression innerKeySelector, LambdaExpression resultSelector) diff --git a/src/EFCore.Relational/Query/SqlExpressions/SelectExpression.cs b/src/EFCore.Relational/Query/SqlExpressions/SelectExpression.cs index ef69d44f5e1..88233016c85 100644 --- a/src/EFCore.Relational/Query/SqlExpressions/SelectExpression.cs +++ b/src/EFCore.Relational/Query/SqlExpressions/SelectExpression.cs @@ -260,23 +260,11 @@ public void ApplyPredicate(SqlExpression expression) } } - public Expression ApplyGrouping(Expression keySelector) + public void ApplyGrouping(Expression keySelector) { ClearOrdering(); - if (keySelector is SqlConstantExpression - || keySelector is SqlParameterExpression) - { - PushdownIntoSubquery(); - var subquery = (SelectExpression)Tables[0]; - var projectionIndex = subquery.AddToProjection((SqlExpression)keySelector, nameof(IGrouping.Key)); - - keySelector = new ColumnExpression(subquery.Projection[projectionIndex], subquery); - } - AppendGroupBy(keySelector); - - return keySelector; } private void AppendGroupBy(Expression keySelector) @@ -284,7 +272,8 @@ private void AppendGroupBy(Expression keySelector) switch (keySelector) { case SqlExpression sqlExpression: - if (!(sqlExpression is SqlConstantExpression)) + if (!(sqlExpression is SqlConstantExpression + || sqlExpression is SqlParameterExpression)) { _groupBy.Add(sqlExpression); } @@ -305,6 +294,12 @@ private void AppendGroupBy(Expression keySelector) } break; + case UnaryExpression unaryExpression + when unaryExpression.NodeType == ExpressionType.Convert + || unaryExpression.NodeType == ExpressionType.ConvertChecked: + AppendGroupBy(unaryExpression.Operand); + break; + default: throw new InvalidOperationException("Invalid keySelector for Group By"); } @@ -1204,7 +1199,8 @@ protected override Expression VisitChildren(ExpressionVisitor visitor) var groupBy = _groupBy.ToList(); _groupBy.Clear(); - _groupBy.AddRange(GroupBy.Select(e => (SqlExpression)visitor.Visit(e)).Where(e => !(e is SqlConstantExpression))); + _groupBy.AddRange(GroupBy.Select(e => (SqlExpression)visitor.Visit(e)) + .Where(e => !(e is SqlConstantExpression || e is SqlParameterExpression))); Having = (SqlExpression)visitor.Visit(Having); @@ -1262,7 +1258,11 @@ protected override Expression VisitChildren(ExpressionVisitor visitor) { var newGroupingKey = (SqlExpression)visitor.Visit(groupingKey); changed |= newGroupingKey != groupingKey; - groupBy.Add(newGroupingKey); + if (!(newGroupingKey is SqlConstantExpression + || newGroupingKey is SqlParameterExpression)) + { + groupBy.Add(newGroupingKey); + } } var havingExpression = (SqlExpression)visitor.Visit(Having); diff --git a/test/EFCore.InMemory.FunctionalTests/Query/AsyncGroupByQueryInMemoryTest.cs b/test/EFCore.InMemory.FunctionalTests/Query/AsyncGroupByQueryInMemoryTest.cs deleted file mode 100644 index a117334e54b..00000000000 --- a/test/EFCore.InMemory.FunctionalTests/Query/AsyncGroupByQueryInMemoryTest.cs +++ /dev/null @@ -1,18 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using Microsoft.EntityFrameworkCore.TestUtilities; -using Xunit.Abstractions; - -namespace Microsoft.EntityFrameworkCore.Query -{ - public class AsyncGroupByQueryInMemoryTest : AsyncGroupByQueryTestBase> - { - // ReSharper disable once UnusedParameter.Local - public AsyncGroupByQueryInMemoryTest(NorthwindQueryInMemoryFixture fixture, ITestOutputHelper testOutputHelper) - : base(fixture) - { - //TestLoggerFactory.TestOutputHelper = testOutputHelper; - } - } -} diff --git a/test/EFCore.Specification.Tests/Query/AsyncGroupByQueryTestBase.cs b/test/EFCore.Specification.Tests/Query/AsyncGroupByQueryTestBase.cs deleted file mode 100644 index 8891c3dfc0a..00000000000 --- a/test/EFCore.Specification.Tests/Query/AsyncGroupByQueryTestBase.cs +++ /dev/null @@ -1,321 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.Linq; -using System.Threading.Tasks; -using Microsoft.EntityFrameworkCore.TestModels.Northwind; -using Microsoft.EntityFrameworkCore.TestUtilities; -using Microsoft.EntityFrameworkCore.TestUtilities.Xunit; -using Xunit; - -// ReSharper disable InconsistentNaming - -namespace Microsoft.EntityFrameworkCore.Query -{ - public abstract class AsyncGroupByQueryTestBase : QueryTestBase - where TFixture : NorthwindQueryFixtureBase, new() - { - protected AsyncGroupByQueryTestBase(TFixture fixture) - : base(fixture) - { - } - - protected NorthwindContext CreateContext() => Fixture.CreateContext(); - - #region GroupByAggregateComposition - - [ConditionalFact(Skip = "Issue #14935. Cannot eval 'GroupBy([o].CustomerID, [o])'")] - public virtual async Task GroupBy_Select_sum_over_unmapped_property() - { - using (var context = CreateContext()) - { - var query = await context.Orders - .GroupBy(o => o.CustomerID) - .Select( - g => new - { - g.Key, - Sum = g.Sum(o => o.Freight) - }) - .ToListAsync(); - - // Do not do deep assertion of result. We don't have data for unmapped property in EF model - Assert.Equal(89, query.Count); - } - } - - [ConditionalFact(Skip = "Issue #14935. Cannot eval 'GroupBy([o].OrderID, [o])'")] - public virtual async Task Select_nested_collection_with_groupby() - { - using (var context = CreateContext()) - { - var expected = (await context.Customers - .Include(c => c.Orders) - // ReSharper disable once StringStartsWithIsCultureSpecific - .Where(c => c.CustomerID.StartsWith("A")) - .ToListAsync()) - .Select( - c => c.Orders.Any() - ? c.Orders.GroupBy(o => o.OrderID).Select(g => g.Key).ToArray() - : Array.Empty()).ToList(); - - var query = context.Customers - // ReSharper disable once StringStartsWithIsCultureSpecific - .Where(c => c.CustomerID.StartsWith("A")) - .Select( - c => c.Orders.Any() - ? c.Orders.GroupBy(o => o.OrderID).Select(g => g.Key).ToArray() - : Array.Empty()); - - var result = await query.ToListAsync(); - - Assert.Equal(expected.Count, result.Count); - } - } - - [ConditionalFact(Skip = "Issue #14935. Cannot eval 'GroupBy([o].CustomerID, new ProjectedType() {Order = [o].OrderID, Customer = [o].CustomerID})'")] - public virtual async Task Select_GroupBy_All() - { - using (var context = CreateContext()) - { - Assert.False( - await context - .Set() - .Select( - o => new ProjectedType - { - Order = o.OrderID, - Customer = o.CustomerID - }) - .GroupBy(a => a.Customer) - .AllAsync(a => a.Key == "ALFKI") - ); - } - } - - private class ProjectedType - { - public int Order { get; set; } - public string Customer { get; set; } - - private bool Equals(ProjectedType other) => Equals(Order, other.Order); - - public override bool Equals(object obj) - => obj is null - ? false - : ReferenceEquals(this, obj) - ? true - : obj.GetType() == GetType() - && Equals((ProjectedType)obj); - - // ReSharper disable once NonReadonlyMemberInGetHashCode - public override int GetHashCode() => Order.GetHashCode(); - } - - #endregion - - #region GroupByWithoutAggregate - - [ConditionalFact(Skip = "Issue #14935. Cannot eval 'GroupBy(new <>f__AnonymousType19`2(CustomerID = [o].CustomerID, OrderDate = [o].OrderDate), [o])'")] - public virtual async Task GroupBy_anonymous_key_without_aggregate() - { - using (var context = CreateContext()) - { - var actual = (await context.Set() - .GroupBy( - o => new - { - o.CustomerID, - o.OrderDate - }) - .Select( - g => new - { - g.Key, - g - }) - .ToListAsync()) - .OrderBy(g => g.Key + " " + g.g.Count()).ToList(); - - var expected = Fixture.QueryAsserter.ExpectedData.Set() - .GroupBy( - o => new - { - o.CustomerID, - o.OrderDate - }) - .Select( - g => new - { - g.Key, - g - }) - .ToList() - .OrderBy(g => g.Key + " " + g.g.Count()).ToList(); - - Assert.Equal(expected.Count, actual.Count); - for (var i = 0; i < expected.Count; i++) - { - Assert.Equal(expected[i].Key, actual[i].Key); - Assert.Equal(expected[i].g.Count(), actual[i].g.Count()); - } - } - } - - #endregion - - #region GroupByEntityType - - [ConditionalFact(Skip = "Issue #14935. Cannot eval 'GroupBy([o].CustomerID, new ProjectedType() {Order = [o].OrderID, Customer = [o].CustomerID})'")] - public virtual async Task Select_GroupBy() - { - using (var context = CreateContext()) - { - var actual = (await context.Set().Select( - o => new ProjectedType - { - Order = o.OrderID, - Customer = o.CustomerID - }).GroupBy(p => p.Customer).ToListAsync()).OrderBy(g => g.Key + " " + g.Count()).ToList(); - - var expected = Fixture.QueryAsserter.ExpectedData.Set().Select( - o => new ProjectedType - { - Order = o.OrderID, - Customer = o.CustomerID - }).GroupBy(p => p.Customer).ToList().OrderBy(g => g.Key + " " + g.Count()).ToList(); - - Assert.Equal(expected.Count, actual.Count); - for (var i = 0; i < expected.Count; i++) - { - Assert.Equal(expected[i].Key, actual[i].Key); - Assert.Equal(expected[i].Count(), actual[i].Count()); - } - } - } - - [ConditionalFact(Skip = "Issue #14935. Cannot eval 'GroupBy([o].OrderID, new ProjectedType() {Order = [o].OrderID, Customer = [o].CustomerID})'")] - public virtual async Task Select_GroupBy_SelectMany() - { - using (var context = CreateContext()) - { - var actual = (await context.Set().Select( - o => new ProjectedType - { - Order = o.OrderID, - Customer = o.CustomerID - }) - .GroupBy(o => o.Order) - .SelectMany(g => g).ToListAsync()).OrderBy(e => e.Order).ToList(); - - var expected = Fixture.QueryAsserter.ExpectedData.Set().Select( - o => new ProjectedType - { - Order = o.OrderID, - Customer = o.CustomerID - }) - .GroupBy(o => o.Order) - .SelectMany(g => g).ToList().OrderBy(e => e.Order).ToList(); - - Assert.Equal(expected.Count, actual.Count); - for (var i = 0; i < expected.Count; i++) - { - Assert.Equal(expected[i], actual[i]); - } - } - } - - [ConditionalFact(Skip = "Issue #14935. Cannot eval 'GroupBy([c], [o])'")] - public virtual async Task Join_GroupBy_entity_ToList() - { - using (var context = CreateContext()) - { - var actual = await (from c in context.Customers.OrderBy(c => c.CustomerID).Take(5) - join o in context.Orders.OrderBy(o => o.OrderID).Take(50) - on c.CustomerID equals o.CustomerID - group o by c - into grp - select new - { - C = grp.Key, - Os = grp.ToList() - }).ToListAsync(); - - var expected = (from c in Fixture.QueryAsserter.ExpectedData.Set() - .OrderBy(c => c.CustomerID).Take(5) - join o in Fixture.QueryAsserter.ExpectedData.Set() - .OrderBy(o => o.OrderID).Take(50) - on c.CustomerID equals o.CustomerID - group o by c - into grp - select new - { - C = grp.Key, - Os = grp.ToList() - }).ToList(); - - Assert.Equal(expected.Count, actual.Count); - - for (var i = 0; i < expected.Count; i++) - { - Assert.Equal(expected[i].C, actual[i].C); - Assert.Equal(expected[i].Os, actual[i].Os); - } - } - } - - #endregion - - #region DoubleGroupBy - - [ConditionalFact(Skip = "Issue #11917")] - public virtual async Task Double_GroupBy_with_aggregate() - { - using (var context = CreateContext()) - { - var actual = await context.Set() - .GroupBy( - o => new - { - o.OrderID, - o.OrderDate - }) - .GroupBy(g => g.Key.OrderDate) - .Select( - g => new - { - g.Key, - Lastest = g.OrderBy(e => e.Key.OrderID).FirstOrDefault() - }) - .ToListAsync(); - - var expected = Fixture.QueryAsserter.ExpectedData.Set() - .GroupBy( - o => new - { - o.OrderID, - o.OrderDate - }) - .GroupBy(g => g.Key.OrderDate) - .Select( - g => new - { - g.Key, - Lastest = g.OrderBy(e => e.Key.OrderID).FirstOrDefault() - }) - .ToList(); - - Assert.Equal(expected.Count, actual.Count); - for (var i = 0; i < expected.Count; i++) - { - Assert.Equal(expected[i].Key, actual[i].Key); - Assert.Equal(expected[i].Lastest.Key, actual[i].Lastest.Key); - Assert.Equal(expected[i].Lastest.Count(), actual[i].Lastest.Count()); - } - } - } - - #endregion - } -} diff --git a/test/EFCore.Specification.Tests/Query/GroupByQueryTestBase.cs b/test/EFCore.Specification.Tests/Query/GroupByQueryTestBase.cs index fd983a05374..8b4eb6d6d2c 100644 --- a/test/EFCore.Specification.Tests/Query/GroupByQueryTestBase.cs +++ b/test/EFCore.Specification.Tests/Query/GroupByQueryTestBase.cs @@ -2,12 +2,10 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; -using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Threading.Tasks; using Microsoft.EntityFrameworkCore.TestModels.Northwind; using Microsoft.EntityFrameworkCore.TestUtilities; -using Microsoft.EntityFrameworkCore.TestUtilities.Xunit; using Xunit; // ReSharper disable InconsistentNaming @@ -820,14 +818,7 @@ protected class CompositeDto public uint? EmployeeId { get; set; } public override bool Equals(object obj) - { - if (obj is null) - { - return false; - } - - return ReferenceEquals(this, obj) ? true : obj.GetType() == GetType() && Equals((CompositeDto)obj); - } + => obj != null && (ReferenceEquals(this, obj) || (obj is CompositeDto dto && Equals(dto))); public override int GetHashCode() => 0; @@ -1053,6 +1044,22 @@ public virtual Task GroupBy_param_with_element_selector_Select_Sum_Min_Key_Max_A e => e.Sum); } + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task GroupBy_anonymous_key_type_mismatch_with_aggregate(bool isAsync) + { + return AssertQuery( + isAsync, + os => os.GroupBy(o => new { I0 = (int?)o.OrderDate.Value.Year }) + .OrderBy(g => g.Key.I0) + .Select(g => new + { + I0 = g.Count(), + I1 = g.Key.I0 + }), + elementSorter: a => a.I1); + } + #endregion #region GroupByWithElementSelectorAggregate @@ -1855,24 +1862,18 @@ public virtual Task GroupBy_aggregate_Pushdown_followed_by_projecting_constant(b .Select(e => 5)); } - [ConditionalFact(Skip = "Issue #14935. Cannot eval 'GroupBy([o].CustomerID, [o])'")] - public virtual void GroupBy_Select_sum_over_unmapped_property() + [ConditionalTheory(Skip = "Issue #14935. Cannot eval 'GroupBy([o].CustomerID, [o])'")] + [MemberData(nameof(IsAsyncData))] + public virtual Task GroupBy_Select_sum_over_unmapped_property(bool isAsync) { - using (var context = CreateContext()) - { - var query = context.Orders - .GroupBy(o => o.CustomerID) - .Select( - g => new - { - g.Key, - Sum = g.Sum(o => o.Freight) - }) - .ToList(); - - // Do not do deep assertion of result. We don't have data for unmapped property in EF model - Assert.Equal(89, query.Count); - } + return AssertQuery( + isAsync, + os => os.GroupBy(o => o.CustomerID) + .Select(g => new + { + g.Key, + Sum = g.Sum(o => o.Freight) + })); } [ConditionalTheory] @@ -2151,55 +2152,32 @@ public virtual Task Distinct_GroupBy_OrderBy_key(bool isAsync) assertOrder: true); } - [ConditionalFact(Skip = "Issue #14935. Cannot eval 'GroupBy([o].OrderID, [o])'")] - public virtual void Select_nested_collection_with_groupby() + [ConditionalTheory(Skip = "Issue #14935. Cannot eval 'GroupBy([o].OrderID, [o])'")] + [MemberData(nameof(IsAsyncData))] + public virtual Task Select_nested_collection_with_groupby(bool isAsync) { - using (var context = CreateContext()) - { - var expected = context.Customers - .Include(c => c.Orders) - // ReSharper disable once StringStartsWithIsCultureSpecific - .Where(c => c.CustomerID.StartsWith("A")) - .ToList() - .Select( - c => c.Orders.Any() - ? c.Orders.GroupBy(o => o.OrderID).Select(g => g.Key).ToArray() - : Array.Empty()).ToList(); - - ClearLog(); - - var query = context.Customers - // ReSharper disable once StringStartsWithIsCultureSpecific - .Where(c => c.CustomerID.StartsWith("A")) + return AssertQuery( + isAsync, + cs => cs.Where(c => c.CustomerID.StartsWith("A")) .Select( c => c.Orders.Any() ? c.Orders.GroupBy(o => o.OrderID).Select(g => g.Key).ToArray() - : Array.Empty()); - - var result = query.ToList(); - - Assert.Equal(expected.Count, result.Count); - } + : Array.Empty())); } - [ConditionalFact(Skip = "Issue #14935. Cannot eval 'GroupBy([o].CustomerID, new ProjectedType() {Order = [o].OrderID, Customer = [o].CustomerID})'")] - public virtual void Select_GroupBy_All() + [ConditionalTheory(Skip = "Issue #14935. Cannot eval 'GroupBy([o].CustomerID, new ProjectedType() {Order = [o].OrderID, Customer = [o].CustomerID})'")] + [MemberData(nameof(IsAsyncData))] + public virtual Task Select_GroupBy_All(bool isAsync) { - using (var context = CreateContext()) - { - Assert.False( - context - .Set() - .Select( - o => new ProjectedType - { - Order = o.OrderID, - Customer = o.CustomerID - }) - .GroupBy(a => a.Customer) - .All(a => a.Key == "ALFKI") - ); - } + return AssertAll>( + isAsync, + os => os.Select(o => new ProjectedType + { + Order = o.OrderID, + Customer = o.CustomerID + }) + .GroupBy(a => a.Customer), + a => a.Key == "ALFKI"); } private class ProjectedType @@ -2680,29 +2658,13 @@ public virtual Task GroupBy_with_aggregate_through_navigation_property(bool isAs elementSorter: e => e.max); } - [ConditionalFact(Skip = "Issue #14935. Cannot eval 'GroupBy(new <>f__AnonymousType19`2(CustomerID = [o].CustomerID, OrderDate = [o].OrderDate), [o])'")] - public virtual void GroupBy_anonymous_key_without_aggregate() + [ConditionalTheory(Skip = "Issue #14935. Cannot eval 'GroupBy(new <>f__AnonymousType19`2(CustomerID = [o].CustomerID, OrderDate = [o].OrderDate), [o])'")] + [MemberData(nameof(IsAsyncData))] + public virtual Task GroupBy_anonymous_key_without_aggregate(bool isAsync) { - using (var context = CreateContext()) - { - var actual = context.Set() - .GroupBy( - o => new - { - o.CustomerID, - o.OrderDate - }) - .Select( - g => new - { - g.Key, - g - }) - .ToList() - .OrderBy(g => g.Key + " " + g.g.Count()).ToList(); - - var expected = Fixture.QueryAsserter.ExpectedData.Set() - .GroupBy( + return AssertQuery( + isAsync, + os => os.GroupBy( o => new { o.CustomerID, @@ -2713,17 +2675,8 @@ public virtual void GroupBy_anonymous_key_without_aggregate() { g.Key, g - }) - .ToList() - .OrderBy(g => g.Key + " " + g.g.Count()).ToList(); - - Assert.Equal(expected.Count, actual.Count); - for (var i = 0; i < expected.Count; i++) - { - Assert.Equal(expected[i].Key, actual[i].Key); - Assert.Equal(expected[i].g.Count(), actual[i].g.Count()); - } - } + }), + elementSorter: g => g.Key + " " + g.g.Count()); } #endregion @@ -2804,131 +2757,67 @@ public virtual Task GroupBy_Select_First_GroupBy_followed_by_identity_projection #region GroupByEntityType - [ConditionalFact(Skip = "Issue #14935. Cannot eval 'GroupBy([o].CustomerID, new ProjectedType() {Order = [o].OrderID, Customer = [o].CustomerID})'")] - public virtual void Select_GroupBy() + [ConditionalTheory(Skip = "Issue #14935. Cannot eval 'GroupBy([o].CustomerID, new ProjectedType() {Order = [o].OrderID, Customer = [o].CustomerID})'")] + [MemberData(nameof(IsAsyncData))] + public virtual Task Select_GroupBy(bool isAsync) { - using (var context = CreateContext()) - { - var actual = context.Set().Select( + return AssertQuery( + isAsync, + os => os.Select( o => new ProjectedType { Order = o.OrderID, Customer = o.CustomerID - }).GroupBy(p => p.Customer).ToList().OrderBy(g => g.Key + " " + g.Count()).ToList(); + }) + .GroupBy(p => p.Customer), + elementSorter: g => g.Key + " " + g.Count()); + } - var expected = Fixture.QueryAsserter.ExpectedData.Set().Select( + [ConditionalTheory(Skip = "Issue #14935. Cannot eval 'GroupBy([o].OrderID, new ProjectedType() {Order = [o].OrderID, Customer = [o].CustomerID})'")] + [MemberData(nameof(IsAsyncData))] + public virtual Task Select_GroupBy_SelectMany(bool isAsync) + { + return AssertQuery( + isAsync, + os => os.Select( o => new ProjectedType { Order = o.OrderID, Customer = o.CustomerID - }).GroupBy(p => p.Customer).ToList().OrderBy(g => g.Key + " " + g.Count()).ToList(); - - Assert.Equal(expected.Count, actual.Count); - for (var i = 0; i < expected.Count; i++) - { - Assert.Equal(expected[i].Key, actual[i].Key); - Assert.Equal(expected[i].Count(), actual[i].Count()); - } - } + }) + .GroupBy(p => p.Customer) + .SelectMany(g => g), + elementSorter: g => g.Order); } - [ConditionalFact(Skip = "Issue #14935. Cannot eval 'GroupBy([o].OrderID, new ProjectedType() {Order = [o].OrderID, Customer = [o].CustomerID})'")] - public virtual void Select_GroupBy_SelectMany() + [ConditionalTheory(Skip = "Issue #14935. Cannot eval 'GroupBy([c], [o])'")] + [MemberData(nameof(IsAsyncData))] + public virtual Task Join_GroupBy_entity_ToList(bool isAsync) { - using (var context = CreateContext()) - { - var actual = context.Set().Select( - o => new ProjectedType - { - Order = o.OrderID, - Customer = o.CustomerID - }) - .GroupBy(o => o.Order) - .SelectMany(g => g).ToList().OrderBy(e => e.Order).ToList(); - - var expected = Fixture.QueryAsserter.ExpectedData.Set().Select( - o => new ProjectedType - { - Order = o.OrderID, - Customer = o.CustomerID - }) - .GroupBy(o => o.Order) - .SelectMany(g => g).ToList().OrderBy(e => e.Order).ToList(); - - Assert.Equal(expected.Count, actual.Count); - for (var i = 0; i < expected.Count; i++) - { - Assert.Equal(expected[i], actual[i]); - } - } - } - - [ConditionalFact(Skip = "Issue #14935. Cannot eval 'GroupBy([c], [o])'")] - public virtual void Join_GroupBy_entity_ToList() - { - using (var context = CreateContext()) - { - var actual = (from c in context.Customers.OrderBy(c => c.CustomerID).Take(5) - join o in context.Orders.OrderBy(o => o.OrderID).Take(50) - on c.CustomerID equals o.CustomerID - group o by c - into grp - select new - { - C = grp.Key, - Os = grp.ToList() - }).ToList(); - - var expected = (from c in Fixture.QueryAsserter.ExpectedData.Set() - .OrderBy(c => c.CustomerID).Take(5) - join o in Fixture.QueryAsserter.ExpectedData.Set() - .OrderBy(o => o.OrderID).Take(50) - on c.CustomerID equals o.CustomerID - group o by c - into grp - select new - { - C = grp.Key, - Os = grp.ToList() - }).ToList(); - - Assert.Equal(expected.Count, actual.Count); - - for (var i = 0; i < expected.Count; i++) - { - Assert.Equal(expected[i].C, actual[i].C); - Assert.Equal(expected[i].Os, actual[i].Os); - } - } + return AssertQuery( + isAsync, + (cs, os) => from c in cs.OrderBy(c => c.CustomerID).Take(5) + join o in os.OrderBy(o => o.OrderID).Take(50) + on c.CustomerID equals o.CustomerID + group o by c into grp + select new + { + C = grp.Key, + Os = grp.ToList() + }); } #endregion #region DoubleGroupBy - [ConditionalFact(Skip = "Issue #14935. Cannot eval 'GroupBy(new <>f__AnonymousType22`2(OrderID = [o].OrderID, OrderDate = [o].OrderDate), [o])'")] - public virtual void Double_GroupBy_with_aggregate() + [ConditionalTheory(Skip = "Issue #14935. Cannot eval 'GroupBy(new <>f__AnonymousType22`2(OrderID = [o].OrderID, OrderDate = [o].OrderDate), [o])'")] + [MemberData(nameof(IsAsyncData))] + public virtual Task Double_GroupBy_with_aggregate(bool isAsync) { - using (var context = CreateContext()) - { - var actual = context.Set() - .GroupBy( - o => new - { - o.OrderID, - o.OrderDate - }) - .GroupBy(g => g.Key.OrderDate) - .Select( - g => new - { - g.Key, - Lastest = g.OrderBy(e => e.Key.OrderID).FirstOrDefault() - }) - .ToList(); - - var expected = Fixture.QueryAsserter.ExpectedData.Set() - .GroupBy( + return AssertQuery( + isAsync, + os => os.GroupBy( o => new { o.OrderID, @@ -2940,17 +2829,7 @@ public virtual void Double_GroupBy_with_aggregate() { g.Key, Lastest = g.OrderBy(e => e.Key.OrderID).FirstOrDefault() - }) - .ToList(); - - Assert.Equal(expected.Count, actual.Count); - for (var i = 0; i < expected.Count; i++) - { - Assert.Equal(expected[i].Key, actual[i].Key); - Assert.Equal(expected[i].Lastest.Key, actual[i].Lastest.Key); - Assert.Equal(expected[i].Lastest.Count(), actual[i].Lastest.Count()); - } - } + })); } #endregion diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/AsyncGroupByQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/AsyncGroupByQuerySqlServerTest.cs deleted file mode 100644 index 97fe4c5ee91..00000000000 --- a/test/EFCore.SqlServer.FunctionalTests/Query/AsyncGroupByQuerySqlServerTest.cs +++ /dev/null @@ -1,20 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using Microsoft.EntityFrameworkCore.TestUtilities; -using Xunit.Abstractions; - -namespace Microsoft.EntityFrameworkCore.Query -{ - public class AsyncGroupByQuerySqlServerTest : AsyncGroupByQueryTestBase> - { - // ReSharper disable once UnusedParameter.Local - public AsyncGroupByQuerySqlServerTest( - NorthwindQuerySqlServerFixture fixture, ITestOutputHelper testOutputHelper) - : base(fixture) - { - Fixture.TestSqlLoggerFactory.Clear(); - //Fixture.TestSqlLoggerFactory.SetTestOutputHelper(testOutputHelper); - } - } -} diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/GroupByQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/GroupByQuerySqlServerTest.cs index 07c73eda44e..bf57f313ef6 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/GroupByQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/GroupByQuerySqlServerTest.cs @@ -500,12 +500,8 @@ public override async Task GroupBy_Constant_Select_Sum_Min_Key_Max_Avg(bool isAs await base.GroupBy_Constant_Select_Sum_Min_Key_Max_Avg(isAsync); AssertSql( - @"SELECT SUM([t].[OrderID]) 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], [o].[CustomerID], [o].[EmployeeID], [o].[OrderDate], 2 AS [Key] - FROM [Orders] AS [o] -) AS [t] -GROUP BY [t].[Key]"); + @"SELECT SUM([o].[OrderID]) 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]"); } public override async Task GroupBy_Constant_with_element_selector_Select_Sum(bool isAsync) @@ -513,12 +509,8 @@ public override async Task GroupBy_Constant_with_element_selector_Select_Sum(boo await base.GroupBy_Constant_with_element_selector_Select_Sum(isAsync); AssertSql( - @"SELECT SUM([t].[OrderID]) AS [Sum] -FROM ( - SELECT [o].[OrderID], [o].[OrderDate], 2 AS [Key] - FROM [Orders] AS [o] -) AS [t] -GROUP BY [t].[Key]"); + @"SELECT SUM([o].[OrderID]) AS [Sum] +FROM [Orders] AS [o]"); } public override async Task GroupBy_Constant_with_element_selector_Select_Sum2(bool isAsync) @@ -526,12 +518,8 @@ public override async Task GroupBy_Constant_with_element_selector_Select_Sum2(bo await base.GroupBy_Constant_with_element_selector_Select_Sum2(isAsync); AssertSql( - @"SELECT SUM([t].[OrderID]) AS [Sum] -FROM ( - SELECT [o].[OrderID], 2 AS [Key] - FROM [Orders] AS [o] -) AS [t] -GROUP BY [t].[Key]"); + @"SELECT SUM([o].[OrderID]) AS [Sum] +FROM [Orders] AS [o]"); } public override async Task GroupBy_Constant_with_element_selector_Select_Sum3(bool isAsync) @@ -539,12 +527,8 @@ public override async Task GroupBy_Constant_with_element_selector_Select_Sum3(bo await base.GroupBy_Constant_with_element_selector_Select_Sum3(isAsync); AssertSql( - @"SELECT SUM([t].[OrderID]) AS [Sum] -FROM ( - SELECT [o].[OrderID], [o].[OrderDate], [o].[CustomerID], 2 AS [Key] - FROM [Orders] AS [o] -) AS [t] -GROUP BY [t].[Key]"); + @"SELECT SUM([o].[OrderID]) AS [Sum] +FROM [Orders] AS [o]"); } public override async Task GroupBy_after_predicate_Constant_Select_Sum_Min_Key_Max_Avg(bool isAsync) @@ -552,13 +536,9 @@ 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(isAsync); AssertSql( - @"SELECT SUM([t].[OrderID]) 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], [o].[CustomerID], [o].[EmployeeID], [o].[OrderDate], 2 AS [Key] - FROM [Orders] AS [o] - WHERE [o].[OrderID] > 10500 -) AS [t] -GROUP BY [t].[Key]"); + @"SELECT SUM([o].[OrderID]) 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"); } public override async Task GroupBy_Constant_with_element_selector_Select_Sum_Min_Key_Max_Avg(bool isAsync) @@ -566,12 +546,8 @@ 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(isAsync); AssertSql( - @"SELECT SUM([t].[OrderID]) AS [Sum], [t].[Key] -FROM ( - SELECT [o].[OrderID], 2 AS [Key] - FROM [Orders] AS [o] -) AS [t] -GROUP BY [t].[Key]"); + @"SELECT SUM([o].[OrderID]) AS [Sum], 2 AS [Key] +FROM [Orders] AS [o]"); } public override async Task GroupBy_param_Select_Sum_Min_Key_Max_Avg(bool isAsync) @@ -581,12 +557,8 @@ public override async Task GroupBy_param_Select_Sum_Min_Key_Max_Avg(bool isAsync AssertSql( @"@__a_0='2' -SELECT SUM([t].[OrderID]) 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], [o].[CustomerID], [o].[EmployeeID], [o].[OrderDate], @__a_0 AS [Key] - FROM [Orders] AS [o] -) AS [t] -GROUP BY [t].[Key]"); +SELECT SUM([o].[OrderID]) 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]"); } public override async Task GroupBy_param_with_element_selector_Select_Sum(bool isAsync) @@ -594,14 +566,8 @@ public override async Task GroupBy_param_with_element_selector_Select_Sum(bool i await base.GroupBy_param_with_element_selector_Select_Sum(isAsync); AssertSql( - @"@__a_0='2' - -SELECT SUM([t].[OrderID]) AS [Sum] -FROM ( - SELECT [o].[OrderID], [o].[OrderDate], @__a_0 AS [Key] - FROM [Orders] AS [o] -) AS [t] -GROUP BY [t].[Key]"); + @"SELECT SUM([o].[OrderID]) AS [Sum] +FROM [Orders] AS [o]"); } public override async Task GroupBy_param_with_element_selector_Select_Sum2(bool isAsync) @@ -609,14 +575,8 @@ public override async Task GroupBy_param_with_element_selector_Select_Sum2(bool await base.GroupBy_param_with_element_selector_Select_Sum2(isAsync); AssertSql( - @"@__a_0='2' - -SELECT SUM([t].[OrderID]) AS [Sum] -FROM ( - SELECT [o].[OrderID], @__a_0 AS [Key] - FROM [Orders] AS [o] -) AS [t] -GROUP BY [t].[Key]"); + @"SELECT SUM([o].[OrderID]) AS [Sum] +FROM [Orders] AS [o]"); } public override async Task GroupBy_param_with_element_selector_Select_Sum3(bool isAsync) @@ -624,14 +584,8 @@ public override async Task GroupBy_param_with_element_selector_Select_Sum3(bool await base.GroupBy_param_with_element_selector_Select_Sum3(isAsync); AssertSql( - @"@__a_0='2' - -SELECT SUM([t].[OrderID]) AS [Sum] -FROM ( - SELECT [o].[OrderID], [o].[OrderDate], [o].[CustomerID], @__a_0 AS [Key] - FROM [Orders] AS [o] -) AS [t] -GROUP BY [t].[Key]"); + @"SELECT SUM([o].[OrderID]) AS [Sum] +FROM [Orders] AS [o]"); } public override async Task GroupBy_param_with_element_selector_Select_Sum_Min_Key_Max_Avg(bool isAsync) @@ -641,12 +595,19 @@ public override async Task GroupBy_param_with_element_selector_Select_Sum_Min_Ke AssertSql( @"@__a_0='2' -SELECT SUM([t].[OrderID]) AS [Sum], [t].[Key] -FROM ( - SELECT [o].[OrderID], @__a_0 AS [Key] - FROM [Orders] AS [o] -) AS [t] -GROUP BY [t].[Key]"); +SELECT SUM([o].[OrderID]) AS [Sum], @__a_0 AS [Key] +FROM [Orders] AS [o]"); + } + + public override async Task GroupBy_anonymous_key_type_mismatch_with_aggregate(bool isAsync) + { + await base.GroupBy_anonymous_key_type_mismatch_with_aggregate(isAsync); + + AssertSql( + @"SELECT COUNT(*) AS [I0], DATEPART(year, [o].[OrderDate]) AS [I1] +FROM [Orders] AS [o] +GROUP BY DATEPART(year, [o].[OrderDate]) +ORDER BY DATEPART(year, [o].[OrderDate])"); } public override async Task GroupBy_Property_scalar_element_selector_Average(bool isAsync) @@ -1258,9 +1219,9 @@ ORDER BY [t].[CustomerID] OFFSET @__p_1 ROWS"); } - public override void GroupBy_Select_sum_over_unmapped_property() + public override async Task GroupBy_Select_sum_over_unmapped_property(bool isAsync) { - base.GroupBy_Select_sum_over_unmapped_property(); + await base.GroupBy_Select_sum_over_unmapped_property(isAsync); AssertSql( @"SELECT [o].[OrderID], [o].[CustomerID], [o].[EmployeeID], [o].[OrderDate] @@ -1446,9 +1407,9 @@ GROUP BY [t].[CustomerID] ORDER BY [t].[CustomerID]"); } - public override void Select_nested_collection_with_groupby() + public override async Task Select_nested_collection_with_groupby(bool isAsync) { - base.Select_nested_collection_with_groupby(); + await base.Select_nested_collection_with_groupby(isAsync); AssertSql( @"SELECT ( @@ -1492,9 +1453,9 @@ FROM [Orders] AS [o1] ORDER BY [o1].[OrderID]"); } - public override void Select_GroupBy_All() + public override async Task Select_GroupBy_All(bool isAsync) { - base.Select_GroupBy_All(); + await base.Select_GroupBy_All(isAsync); AssertSql( @"SELECT [o].[OrderID] AS [Order], [o].[CustomerID] AS [Customer] @@ -1831,9 +1792,9 @@ FROM [Orders] AS [c] FROM [Customers] AS [i.Customer0]"); } - public override void GroupBy_anonymous_key_without_aggregate() + public override async Task GroupBy_anonymous_key_without_aggregate(bool isAsync) { - base.GroupBy_anonymous_key_without_aggregate(); + await base.GroupBy_anonymous_key_without_aggregate(isAsync); AssertSql( @"SELECT [o].[OrderID], [o].[CustomerID], [o].[EmployeeID], [o].[OrderDate] @@ -1892,9 +1853,9 @@ public override async Task GroupBy_Select_First_GroupBy_followed_by_identity_pro ""); } - public override void Select_GroupBy() + public override async Task Select_GroupBy(bool isAsync) { - base.Select_GroupBy(); + await base.Select_GroupBy(isAsync); AssertSql( @"SELECT [o].[OrderID] AS [Order], [o].[CustomerID] AS [Customer] @@ -1902,9 +1863,9 @@ FROM [Orders] AS [o] ORDER BY [o].[CustomerID]"); } - public override void Select_GroupBy_SelectMany() + public override async Task Select_GroupBy_SelectMany(bool isAsync) { - base.Select_GroupBy_SelectMany(); + await base.Select_GroupBy_SelectMany(isAsync); AssertSql( @"SELECT [o].[OrderID] AS [Order], [o].[CustomerID] AS [Customer] @@ -1912,9 +1873,9 @@ FROM [Orders] AS [o] ORDER BY [o].[OrderID]"); } - public override void Join_GroupBy_entity_ToList() + public override async Task Join_GroupBy_entity_ToList(bool isAsync) { - base.Join_GroupBy_entity_ToList(); + await base.Join_GroupBy_entity_ToList(isAsync); AssertSql( @"@__p_0='5' @@ -1933,9 +1894,9 @@ ORDER BY [o0].[OrderID] ) AS [t2] ON [t1].[CustomerID] = [t2].[CustomerID]"); } - public override void Double_GroupBy_with_aggregate() + public override async Task Double_GroupBy_with_aggregate(bool isAsync) { - base.Double_GroupBy_with_aggregate(); + await base.Double_GroupBy_with_aggregate(isAsync); AssertSql( @"SELECT [o0].[OrderID], [o0].[CustomerID], [o0].[EmployeeID], [o0].[OrderDate] diff --git a/test/EFCore.Sqlite.FunctionalTests/Query/AsyncGroupByQuerySqliteTest.cs b/test/EFCore.Sqlite.FunctionalTests/Query/AsyncGroupByQuerySqliteTest.cs deleted file mode 100644 index ea78bc45977..00000000000 --- a/test/EFCore.Sqlite.FunctionalTests/Query/AsyncGroupByQuerySqliteTest.cs +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using Microsoft.EntityFrameworkCore.TestUtilities; -using Xunit.Abstractions; - -namespace Microsoft.EntityFrameworkCore.Query -{ - public class AsyncGroupByQuerySqliteTest : AsyncGroupByQueryTestBase> - { - // ReSharper disable once UnusedParameter.Local - public AsyncGroupByQuerySqliteTest(NorthwindQuerySqliteFixture fixture, ITestOutputHelper testOutputHelper) - : base(fixture) - { - Fixture.TestSqlLoggerFactory.Clear(); - //Fixture.TestSqlLoggerFactory.SetTestOutputHelper(testOutputHelper); - } - } -}