Skip to content

Commit 6c07336

Browse files
committed
Query: Support for GroupBy entity type
Resolves #17653
1 parent e178c29 commit 6c07336

File tree

8 files changed

+259
-282
lines changed

8 files changed

+259
-282
lines changed

src/EFCore.InMemory/Query/Internal/InMemoryQueryExpression.cs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -870,6 +870,22 @@ private static Expression GetGroupingKey(Expression key, List<Expression> groupi
870870

871871
return memberInitExpression.Update(updatedNewExpression, memberBindings);
872872

873+
case EntityShaperExpression entityShaperExpression
874+
when entityShaperExpression.ValueBufferExpression is ProjectionBindingExpression projectionBindingExpression:
875+
var entityProjectionExpression = (EntityProjectionExpression)((InMemoryQueryExpression)projectionBindingExpression.QueryExpression)
876+
.GetProjection(projectionBindingExpression);
877+
var readExpressions = new Dictionary<IProperty, MethodCallExpression>();
878+
foreach (var property in GetAllPropertiesInHierarchy(entityProjectionExpression.EntityType))
879+
{
880+
readExpressions[property] = (MethodCallExpression)GetGroupingKey(
881+
entityProjectionExpression.BindProperty(property),
882+
groupingExpressions,
883+
groupingKeyAccessExpression);
884+
}
885+
886+
return entityShaperExpression.Update(
887+
new EntityProjectionExpression(entityProjectionExpression.EntityType, readExpressions));
888+
873889
default:
874890
var index = groupingExpressions.Count;
875891
groupingExpressions.Add(key);

src/EFCore.InMemory/Query/Internal/InMemoryQueryableMethodTranslatingExpressionVisitor.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -457,6 +457,10 @@ private static ShapedQueryExpression CreateShapedQueryExpressionStatic(IEntityTy
457457

458458
return memberInitExpression.Update(updatedNewExpression, newBindings);
459459

460+
case EntityShaperExpression entityShaperExpression
461+
when entityShaperExpression.ValueBufferExpression is ProjectionBindingExpression projectionBindingExpression:
462+
return entityShaperExpression;
463+
460464
default:
461465
var translation = TranslateExpression(expression);
462466
if (translation == null)

src/EFCore.Relational/Query/RelationalQueryableMethodTranslatingExpressionVisitor.cs

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -452,7 +452,16 @@ private static ShapedQueryExpression CreateShapedQueryExpression(IEntityType ent
452452
var translatedKey = TranslateGroupingKey(remappedKeySelector);
453453
if (translatedKey == null)
454454
{
455-
return null;
455+
// This could be group by entity type
456+
if (remappedKeySelector is not EntityShaperExpression
457+
{ ValueBufferExpression : ProjectionBindingExpression })
458+
{
459+
// ValueBufferExpression can be JsonQuery, ProjectionBindingExpression, EntityProjection
460+
// We only allow ProjectionBindingExpression which represents a regular entity
461+
return null;
462+
}
463+
464+
translatedKey = remappedKeySelector;
456465
}
457466

458467
if (elementSelector != null)

src/EFCore.Relational/Query/SqlExpressions/SelectExpression.cs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2003,6 +2003,23 @@ private static void PopulateGroupByTerms(
20032003
PopulateGroupByTerms(unaryExpression.Operand, groupByTerms, groupByAliases, name);
20042004
break;
20052005

2006+
case EntityShaperExpression entityShaperExpression
2007+
when entityShaperExpression.ValueBufferExpression is ProjectionBindingExpression projectionBindingExpression:
2008+
var entityProjectionExpression = (EntityProjectionExpression)((SelectExpression)projectionBindingExpression.QueryExpression)
2009+
.GetProjection(projectionBindingExpression);
2010+
foreach (var property in GetAllPropertiesInHierarchy(entityProjectionExpression.EntityType))
2011+
{
2012+
PopulateGroupByTerms(entityProjectionExpression.BindProperty(property), groupByTerms, groupByAliases, name: null);
2013+
}
2014+
2015+
if (entityProjectionExpression.DiscriminatorExpression != null)
2016+
{
2017+
PopulateGroupByTerms(
2018+
entityProjectionExpression.DiscriminatorExpression, groupByTerms, groupByAliases, name: DiscriminatorColumnAlias);
2019+
}
2020+
2021+
break;
2022+
20062023
default:
20072024
throw new InvalidOperationException(RelationalStrings.InvalidKeySelectorForGroupBy(keySelector, keySelector.GetType()));
20082025
}

test/EFCore.Specification.Tests/Query/Ef6GroupByTestBase.cs

Lines changed: 73 additions & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -109,11 +109,18 @@ public virtual Task GroupBy_is_optimized_when_grouping_by_row_and_projecting_col
109109
[ConditionalTheory]
110110
[MemberData(nameof(IsAsyncData))]
111111
public virtual Task Grouping_by_all_columns_doesnt_produce_a_groupby_statement(bool async)
112-
// GroupBy entityType. Issue #17653.
113-
=> AssertTranslationFailed(
114-
() => AssertQuery(
115-
async,
116-
ss => ss.Set<ArubaOwner>().GroupBy(o => o).Select(g => g.Key)));
112+
=> AssertQuery(
113+
async,
114+
ss => ss.Set<ArubaOwner>().GroupBy(o => o).Select(g => g.Key),
115+
elementSorter: e => e.Id,
116+
elementAsserter: (e, a) =>
117+
{
118+
Assert.Equal(e.Id, a.Id);
119+
Assert.Equal(e.Alias, a.Alias);
120+
Assert.Equal(e.FirstName, a.FirstName);
121+
Assert.Equal(e.LastName, a.LastName);
122+
},
123+
entryCount: 10);
117124

118125
[ConditionalTheory]
119126
[MemberData(nameof(IsAsyncData))]
@@ -132,111 +139,93 @@ public virtual Task Grouping_by_all_columns_with_aggregate_function_works_1(bool
132139
[ConditionalTheory]
133140
[MemberData(nameof(IsAsyncData))]
134141
public virtual Task Grouping_by_all_columns_with_aggregate_function_works_2(bool async)
135-
// GroupBy entityType. Issue #17653.
136-
=> AssertTranslationFailed(
137-
() => AssertQueryScalar(
138-
async,
139-
ss => ss.Set<ArubaOwner>().GroupBy(o => o, c => new { c.LastName, c.FirstName }, (k, g) => g.Count())));
142+
=> AssertQueryScalar(
143+
async,
144+
ss => ss.Set<ArubaOwner>().GroupBy(o => o, c => new { c.LastName, c.FirstName }, (k, g) => g.Count()));
140145

141146
[ConditionalTheory]
142147
[MemberData(nameof(IsAsyncData))]
143148
public virtual Task Grouping_by_all_columns_with_aggregate_function_works_3(bool async)
144-
// GroupBy entityType. Issue #17653.
145-
=> AssertTranslationFailed(
146-
() => AssertQueryScalar(
147-
async,
148-
ss => ss.Set<ArubaOwner>().GroupBy(o => o, c => c, (k, g) => g.Count())));
149+
=> AssertQueryScalar(
150+
async,
151+
ss => ss.Set<ArubaOwner>().GroupBy(o => o, c => c, (k, g) => g.Count()));
149152

150153
[ConditionalTheory]
151154
[MemberData(nameof(IsAsyncData))]
152155
public virtual Task Grouping_by_all_columns_with_aggregate_function_works_4(bool async)
153-
// GroupBy entityType. Issue #17653.
154-
=> AssertTranslationFailed(
155-
() => AssertQuery(
156-
async,
157-
ss => ss.Set<ArubaOwner>().GroupBy(o => o, c => c, (k, g) => new { Count = g.Count() })));
156+
=> AssertQuery(
157+
async,
158+
ss => ss.Set<ArubaOwner>().GroupBy(o => o, c => c, (k, g) => new { Count = g.Count() }));
158159

159160
[ConditionalTheory]
160161
[MemberData(nameof(IsAsyncData))]
161162
public virtual Task Grouping_by_all_columns_with_aggregate_function_works_5(bool async)
162-
// GroupBy entityType. Issue #17653.
163-
=> AssertTranslationFailed(
164-
() => AssertQuery(
165-
async,
166-
ss => ss.Set<ArubaOwner>().GroupBy(o => o, c => c, (k, g) => new { k.Id, Count = g.Count() })));
163+
=> AssertQuery(
164+
async,
165+
ss => ss.Set<ArubaOwner>().GroupBy(o => o, c => c, (k, g) => new { k.Id, Count = g.Count() }));
167166

168167
[ConditionalTheory]
169168
[MemberData(nameof(IsAsyncData))]
170169
public virtual Task Grouping_by_all_columns_with_aggregate_function_works_6(bool async)
171-
// GroupBy entityType. Issue #17653.
172-
=> AssertTranslationFailed(
173-
() => AssertQuery(
174-
async,
175-
ss => ss.Set<ArubaOwner>().GroupBy(
176-
o => o, c => c, (k, g) => new
177-
{
178-
k.Id,
179-
k.Alias,
180-
Count = g.Count()
181-
})));
170+
=> AssertQuery(
171+
async,
172+
ss => ss.Set<ArubaOwner>().GroupBy(
173+
o => o, c => c, (k, g) => new
174+
{
175+
k.Id,
176+
k.Alias,
177+
Count = g.Count()
178+
}));
182179

183180
[ConditionalTheory]
184181
[MemberData(nameof(IsAsyncData))]
185182
public virtual Task Grouping_by_all_columns_with_aggregate_function_works_7(bool async)
186-
// GroupBy entityType. Issue #17653.
187-
=> AssertTranslationFailed(
188-
() => AssertQueryScalar(
189-
async,
190-
ss => from o in ss.Set<ArubaOwner>()
191-
group o by o
192-
into g
193-
select g.Count()));
183+
=> AssertQueryScalar(
184+
async,
185+
ss => from o in ss.Set<ArubaOwner>()
186+
group o by o
187+
into g
188+
select g.Count());
194189

195190
[ConditionalTheory]
196191
[MemberData(nameof(IsAsyncData))]
197192
public virtual Task Grouping_by_all_columns_with_aggregate_function_works_8(bool async)
198-
// GroupBy entityType. Issue #17653.
199-
=> AssertTranslationFailed(
200-
() => AssertQuery(
201-
async,
202-
ss => from o in ss.Set<ArubaOwner>()
203-
group o by o
204-
into g
205-
select new { g.Key.Id, Count = g.Count() }));
193+
=> AssertQuery(
194+
async,
195+
ss => from o in ss.Set<ArubaOwner>()
196+
group o by o
197+
into g
198+
select new { g.Key.Id, Count = g.Count() });
206199

207200
[ConditionalTheory]
208201
[MemberData(nameof(IsAsyncData))]
209202
public virtual Task Grouping_by_all_columns_with_aggregate_function_works_9(bool async)
210-
// GroupBy entityType. Issue #17653.
211-
=> AssertTranslationFailed(
212-
() => AssertQuery(
213-
async,
214-
ss => from o in ss.Set<ArubaOwner>()
215-
group o by o
216-
into g
217-
select new
218-
{
219-
g.Key.Id,
220-
g.Key.Alias,
221-
Count = g.Count()
222-
}));
203+
=> AssertQuery(
204+
async,
205+
ss => from o in ss.Set<ArubaOwner>()
206+
group o by o
207+
into g
208+
select new
209+
{
210+
g.Key.Id,
211+
g.Key.Alias,
212+
Count = g.Count()
213+
});
223214

224215
[ConditionalTheory]
225216
[MemberData(nameof(IsAsyncData))]
226217
public virtual Task Grouping_by_all_columns_with_aggregate_function_works_10(bool async)
227-
// GroupBy entityType. Issue #17653.
228-
=> AssertTranslationFailed(
229-
() => AssertQuery(
230-
async,
231-
ss => from o in ss.Set<ArubaOwner>()
232-
group o by o
233-
into g
234-
select new
235-
{
236-
g.Key.Id,
237-
Sum = g.Sum(x => x.Id),
238-
Count = g.Count()
239-
}));
218+
=> AssertQuery(
219+
async,
220+
ss => from o in ss.Set<ArubaOwner>()
221+
group o by o
222+
into g
223+
select new
224+
{
225+
g.Key.Id,
226+
Sum = g.Sum(x => x.Id),
227+
Count = g.Count()
228+
});
240229

241230
[ConditionalTheory]
242231
[MemberData(nameof(IsAsyncData))]
@@ -736,7 +725,6 @@ public virtual Task Whats_new_2021_sample_13(bool async)
736725
[ConditionalTheory] // From #12088
737726
[MemberData(nameof(IsAsyncData))]
738727
public virtual Task Whats_new_2021_sample_14(bool async)
739-
// GroupBy entityType. Issue #17653.
740728
=> AssertTranslationFailed(
741729
() => AssertQuery(
742730
async,
@@ -747,13 +735,12 @@ public virtual Task Whats_new_2021_sample_14(bool async)
747735
[ConditionalTheory] // From #12088
748736
[MemberData(nameof(IsAsyncData))]
749737
public virtual Task Whats_new_2021_sample_15(bool async)
750-
// GroupBy entityType. Issue #17653.
751-
=> AssertTranslationFailed(
752-
() => AssertQuery(
753-
async,
754-
ss => ss.Set<Person>()
755-
.GroupBy(bp => bp.Feet)
756-
.Select(g => g.OrderByDescending(bp => bp.Id).FirstOrDefault())));
738+
=> AssertQuery(
739+
async,
740+
ss => ss.Set<Person>()
741+
.GroupBy(bp => bp.Feet)
742+
.Select(g => g.OrderByDescending(bp => bp.Id).FirstOrDefault()),
743+
entryCount: 12);
757744

758745
[ConditionalTheory] // From #12573
759746
[MemberData(nameof(IsAsyncData))]

0 commit comments

Comments
 (0)