Skip to content

Commit 9e1bb8a

Browse files
committed
Query: Support for GroupBy entity type
Resolves #17653
1 parent be26865 commit 9e1bb8a

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
@@ -455,6 +455,10 @@ private static ShapedQueryExpression CreateShapedQueryExpressionStatic(IEntityTy
455455

456456
return memberInitExpression.Update(updatedNewExpression, newBindings);
457457

458+
case EntityShaperExpression entityShaperExpression
459+
when entityShaperExpression.ValueBufferExpression is ProjectionBindingExpression projectionBindingExpression:
460+
return entityShaperExpression;
461+
458462
default:
459463
var translation = TranslateExpression(expression);
460464
if (translation == null)

src/EFCore.Relational/Query/RelationalQueryableMethodTranslatingExpressionVisitor.cs

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

456465
if (elementSelector != null)

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

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

1860+
case EntityShaperExpression entityShaperExpression
1861+
when entityShaperExpression.ValueBufferExpression is ProjectionBindingExpression projectionBindingExpression:
1862+
var entityProjectionExpression = (EntityProjectionExpression)((SelectExpression)projectionBindingExpression.QueryExpression)
1863+
.GetProjection(projectionBindingExpression);
1864+
foreach (var property in GetAllPropertiesInHierarchy(entityProjectionExpression.EntityType))
1865+
{
1866+
PopulateGroupByTerms(entityProjectionExpression.BindProperty(property), groupByTerms, groupByAliases, name: null);
1867+
}
1868+
1869+
if (entityProjectionExpression.DiscriminatorExpression != null)
1870+
{
1871+
PopulateGroupByTerms(
1872+
entityProjectionExpression.DiscriminatorExpression, groupByTerms, groupByAliases, name: DiscriminatorColumnAlias);
1873+
}
1874+
1875+
break;
1876+
18601877
default:
18611878
throw new InvalidOperationException(RelationalStrings.InvalidKeySelectorForGroupBy(keySelector, keySelector.GetType()));
18621879
}

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))]
@@ -731,7 +720,6 @@ public virtual Task Whats_new_2021_sample_13(bool async)
731720
[ConditionalTheory] // From #12088
732721
[MemberData(nameof(IsAsyncData))]
733722
public virtual Task Whats_new_2021_sample_14(bool async)
734-
// GroupBy entityType. Issue #17653.
735723
=> AssertTranslationFailed(
736724
() => AssertQuery(
737725
async,
@@ -742,13 +730,12 @@ public virtual Task Whats_new_2021_sample_14(bool async)
742730
[ConditionalTheory] // From #12088
743731
[MemberData(nameof(IsAsyncData))]
744732
public virtual Task Whats_new_2021_sample_15(bool async)
745-
// GroupBy entityType. Issue #17653.
746-
=> AssertTranslationFailed(
747-
() => AssertQuery(
748-
async,
749-
ss => ss.Set<Person>()
750-
.GroupBy(bp => bp.Feet)
751-
.Select(g => g.OrderByDescending(bp => bp.Id).FirstOrDefault())));
733+
=> AssertQuery(
734+
async,
735+
ss => ss.Set<Person>()
736+
.GroupBy(bp => bp.Feet)
737+
.Select(g => g.OrderByDescending(bp => bp.Id).FirstOrDefault()),
738+
entryCount: 12);
752739

753740
[ConditionalTheory] // From #12573
754741
[MemberData(nameof(IsAsyncData))]

0 commit comments

Comments
 (0)