Skip to content

Commit

Permalink
Query: Support for GroupBy entity type
Browse files Browse the repository at this point in the history
Resolves #17653
  • Loading branch information
smitpatel committed Sep 7, 2022
1 parent be26865 commit 1d629b2
Show file tree
Hide file tree
Showing 6 changed files with 129 additions and 65 deletions.
16 changes: 16 additions & 0 deletions src/EFCore.InMemory/Query/Internal/InMemoryQueryExpression.cs
Original file line number Diff line number Diff line change
Expand Up @@ -870,6 +870,22 @@ private static Expression GetGroupingKey(Expression key, List<Expression> groupi

return memberInitExpression.Update(updatedNewExpression, memberBindings);

case EntityShaperExpression entityShaperExpression
when entityShaperExpression.ValueBufferExpression is ProjectionBindingExpression projectionBindingExpression:
var entityProjectionExpression = (EntityProjectionExpression)((InMemoryQueryExpression)projectionBindingExpression.QueryExpression)
.GetProjection(projectionBindingExpression);
var readExpressions = new Dictionary<IProperty, MethodCallExpression>();
foreach (var property in GetAllPropertiesInHierarchy(entityProjectionExpression.EntityType))
{
readExpressions[property] = (MethodCallExpression)GetGroupingKey(
entityProjectionExpression.BindProperty(property),
groupingExpressions,
groupingKeyAccessExpression);
}

return entityShaperExpression.Update(
new EntityProjectionExpression(entityProjectionExpression.EntityType, readExpressions));

default:
var index = groupingExpressions.Count;
groupingExpressions.Add(key);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -455,6 +455,10 @@ private static ShapedQueryExpression CreateShapedQueryExpressionStatic(IEntityTy

return memberInitExpression.Update(updatedNewExpression, newBindings);

case EntityShaperExpression entityShaperExpression
when entityShaperExpression.ValueBufferExpression is ProjectionBindingExpression projectionBindingExpression:
return entityShaperExpression;

default:
var translation = TranslateExpression(expression);
if (translation == null)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -450,7 +450,16 @@ private static ShapedQueryExpression CreateShapedQueryExpression(IEntityType ent
var translatedKey = TranslateGroupingKey(remappedKeySelector);
if (translatedKey == null)
{
return null;
// This could be group by entity type
if (remappedKeySelector is not EntityShaperExpression
{ ValueBufferExpression : ProjectionBindingExpression })
{
// ValueBufferExpression can be JsonQuery, ProjectionBindingExpression, EntityProjection
// We only allow ProjectionBindingExpression which represents a regular entity
return null;
}

translatedKey = remappedKeySelector;
}

if (elementSelector != null)
Expand Down
17 changes: 17 additions & 0 deletions src/EFCore.Relational/Query/SqlExpressions/SelectExpression.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1857,6 +1857,23 @@ private static void PopulateGroupByTerms(
PopulateGroupByTerms(unaryExpression.Operand, groupByTerms, groupByAliases, name);
break;

case EntityShaperExpression entityShaperExpression
when entityShaperExpression.ValueBufferExpression is ProjectionBindingExpression projectionBindingExpression:
var entityProjectionExpression = (EntityProjectionExpression)((SelectExpression)projectionBindingExpression.QueryExpression)
.GetProjection(projectionBindingExpression);
foreach (var property in GetAllPropertiesInHierarchy(entityProjectionExpression.EntityType))
{
PopulateGroupByTerms(entityProjectionExpression.BindProperty(property), groupByTerms, groupByAliases, name: null);
}

if (entityProjectionExpression.DiscriminatorExpression != null)
{
PopulateGroupByTerms(
entityProjectionExpression.DiscriminatorExpression, groupByTerms, groupByAliases, name: DiscriminatorColumnAlias);
}

break;

default:
throw new InvalidOperationException(RelationalStrings.InvalidKeySelectorForGroupBy(keySelector, keySelector.GetType()));
}
Expand Down
127 changes: 66 additions & 61 deletions test/EFCore.Specification.Tests/Query/NorthwindGroupByQueryTestBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2688,6 +2688,19 @@ public virtual Task GroupBy_selecting_grouping_key_list(bool async)
AssertCollection(e.Data, a.Data);
});
[ConditionalTheory]
[MemberData(nameof(IsAsyncData))]
public virtual Task Select_GroupBy_SelectMany(bool async)
// Entity equality. Issue #15938.
=> AssertTranslationFailed(
() => AssertQuery(
async,
ss => ss.Set<Order>().Select(
o => new ProjectedType { Order = o.OrderID, Customer = o.CustomerID })
.GroupBy(p => p.Customer)
.SelectMany(g => g),
elementSorter: g => g.Order));
#endregion
#region GroupBySelectFirst
Expand Down Expand Up @@ -2788,81 +2801,73 @@ public virtual Task GroupBy_select_grouping_composed_list_2(bool async)
#region GroupByEntityType
[ConditionalTheory]
[MemberData(nameof(IsAsyncData))]
public virtual Task Select_GroupBy_SelectMany(bool async)
// Entity equality. Issue #15938.
=> AssertTranslationFailed(
() => AssertQuery(
async,
ss => ss.Set<Order>().Select(
o => new ProjectedType { Order = o.OrderID, Customer = o.CustomerID })
.GroupBy(p => p.Customer)
.SelectMany(g => g),
elementSorter: g => g.Order));
[ConditionalTheory]
[MemberData(nameof(IsAsyncData))]
public virtual Task GroupBy_with_group_key_being_navigation(bool async)
// Entity equality. Issue #15938.
=> AssertTranslationFailed(
() => AssertQuery(
async,
ss => ss.Set<OrderDetail>()
.GroupBy(od => od.Order)
.Select(g => new { g.Key, Aggregate = g.Sum(od => od.OrderID) }),
elementSorter: e => e.Key));
=> AssertQuery(
async,
ss => ss.Set<OrderDetail>()
.GroupBy(od => od.Order)
.Select(g => new { g.Key, Aggregate = g.Sum(od => od.OrderID) }),
elementSorter: e => e.Key.OrderID,
elementAsserter: (e, a) =>
{
AssertEqual(e.Key, a.Key);
AssertEqual(e.Aggregate, a.Aggregate);
},
entryCount: 830);
[ConditionalTheory]
[MemberData(nameof(IsAsyncData))]
public virtual Task GroupBy_with_group_key_being_nested_navigation(bool async)
// Entity equality. Issue #15938.
=> AssertTranslationFailed(
() => AssertQuery(
async,
ss => ss.Set<OrderDetail>()
.GroupBy(od => od.Order.Customer)
.Select(g => new { g.Key, Aggregate = g.Sum(od => od.OrderID) }),
elementSorter: e => e.Key));
=> AssertQuery(
async,
ss => ss.Set<OrderDetail>()
.GroupBy(od => od.Order.Customer)
.Select(g => new { g.Key, Aggregate = g.Sum(od => od.OrderID) }),
elementSorter: e => e.Key.CustomerID,
elementAsserter: (e, a) =>
{
AssertEqual(e.Key, a.Key);
AssertEqual(e.Aggregate, a.Aggregate);
},
entryCount: 89);
[ConditionalTheory]
[MemberData(nameof(IsAsyncData))]
public virtual Task GroupBy_with_group_key_being_navigation_with_entity_key_projection(bool async)
// Entity equality. Issue #15938.
=> AssertTranslationFailed(
() => AssertQuery(
async,
ss => ss.Set<OrderDetail>()
.GroupBy(od => od.Order)
.Select(g => g.Key)));
=> AssertQuery(
async,
ss => ss.Set<OrderDetail>()
.GroupBy(od => od.Order)
.Select(g => g.Key),
entryCount: 830);

[ConditionalTheory]
[ConditionalTheory(Skip = "Issue#29014")]
[MemberData(nameof(IsAsyncData))]
public virtual Task GroupBy_with_group_key_being_navigation_with_complex_projection(bool async)
// Entity equality. Issue #15938.
=> AssertTranslationFailed(
() => AssertQuery(
async,
ss => ss.Set<OrderDetail>()
.GroupBy(od => od.Order)
.Select(
g => new
{
g.Key,
Id1 = g.Key.CustomerID,
Id2 = g.Key.Customer.CustomerID,
Id3 = g.Key.OrderID,
Aggregate = g.Sum(od => od.OrderID)
}),
elementSorter: e => e.Id3,
elementAsserter: (e, a) =>
{
AssertEqual(e.Key, a.Key);
Assert.Equal(e.Id1, a.Id1);
Assert.Equal(e.Id2, a.Id2);
Assert.Equal(e.Id3, a.Id3);
Assert.Equal(e.Aggregate, a.Aggregate);
}));
=> AssertQuery(
async,
ss => ss.Set<OrderDetail>()
.GroupBy(od => od.Order)
.Select(
g => new
{
g.Key,
Id1 = g.Key.CustomerID,
Id2 = g.Key.Customer.CustomerID,
Id3 = g.Key.OrderID,
Aggregate = g.Sum(od => od.OrderID)
}),
elementSorter: e => e.Id3,
elementAsserter: (e, a) =>
{
AssertEqual(e.Key, a.Key);
Assert.Equal(e.Id1, a.Id1);
Assert.Equal(e.Id2, a.Id2);
Assert.Equal(e.Id3, a.Id3);
Assert.Equal(e.Aggregate, a.Aggregate);
});

#endregion

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2464,21 +2464,34 @@ public override async Task GroupBy_with_group_key_being_navigation(bool async)
{
await base.GroupBy_with_group_key_being_navigation(async);

AssertSql();
AssertSql(
@"SELECT [o0].[OrderID], [o0].[CustomerID], [o0].[EmployeeID], [o0].[OrderDate], COALESCE(SUM([o].[OrderID]), 0) AS [Aggregate]
FROM [Order Details] AS [o]
INNER JOIN [Orders] AS [o0] ON [o].[OrderID] = [o0].[OrderID]
GROUP BY [o0].[OrderID], [o0].[CustomerID], [o0].[EmployeeID], [o0].[OrderDate]");
}

public override async Task GroupBy_with_group_key_being_nested_navigation(bool async)
{
await base.GroupBy_with_group_key_being_nested_navigation(async);

AssertSql();
AssertSql(
@"SELECT [c].[CustomerID], [c].[Address], [c].[City], [c].[CompanyName], [c].[ContactName], [c].[ContactTitle], [c].[Country], [c].[Fax], [c].[Phone], [c].[PostalCode], [c].[Region], COALESCE(SUM([o].[OrderID]), 0) AS [Aggregate]
FROM [Order Details] AS [o]
INNER JOIN [Orders] AS [o0] ON [o].[OrderID] = [o0].[OrderID]
LEFT JOIN [Customers] AS [c] ON [o0].[CustomerID] = [c].[CustomerID]
GROUP BY [c].[CustomerID], [c].[Address], [c].[City], [c].[CompanyName], [c].[ContactName], [c].[ContactTitle], [c].[Country], [c].[Fax], [c].[Phone], [c].[PostalCode], [c].[Region]");
}

public override async Task GroupBy_with_group_key_being_navigation_with_entity_key_projection(bool async)
{
await base.GroupBy_with_group_key_being_navigation_with_entity_key_projection(async);

AssertSql();
AssertSql(
@"SELECT [o0].[OrderID], [o0].[CustomerID], [o0].[EmployeeID], [o0].[OrderDate]
FROM [Order Details] AS [o]
INNER JOIN [Orders] AS [o0] ON [o].[OrderID] = [o0].[OrderID]
GROUP BY [o0].[OrderID], [o0].[CustomerID], [o0].[EmployeeID], [o0].[OrderDate]");
}

public override async Task GroupBy_with_group_key_being_navigation_with_complex_projection(bool async)
Expand Down

0 comments on commit 1d629b2

Please sign in to comment.