Skip to content

Commit 66d135f

Browse files
committed
Fix to #12173 - Query: Include annotation is not ignored for group by aggregate causing client eval
Problem was that when looking for query source on which the include should be applied we were not short-circuiting on subqueries with aggregate result operators that returns a scalar value (count, sum, etc). Fix is to jump out of the QuerySourceTracingExpressionVisitor when we encounter such a case.
1 parent 6e69cad commit 66d135f

File tree

3 files changed

+265
-0
lines changed

3 files changed

+265
-0
lines changed

src/EFCore.Specification.Tests/Query/GearsOfWarQueryTestBase.cs

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5850,6 +5850,136 @@ public virtual void Time_of_day_datetimeoffset()
58505850
select m.Timeline.TimeOfDay);
58515851
}
58525852

5853+
[ConditionalFact]
5854+
public virtual void GroupBy_Property_Include_Select_Average()
5855+
{
5856+
AssertQueryScalar<Gear>(gs => gs.Include(g => g.CityOfBirth ).GroupBy(g => g.Rank).Select(g => g.Average(gg => gg.SquadId)));
5857+
}
5858+
5859+
[ConditionalFact]
5860+
public virtual void GroupBy_Property_Include_Select_Sum()
5861+
{
5862+
AssertQueryScalar<Gear>(gs => gs.Include(g => g.CityOfBirth).GroupBy(g => g.Rank).Select(g => g.Sum(gg => gg.SquadId)));
5863+
}
5864+
5865+
[ConditionalFact]
5866+
public virtual void GroupBy_Property_Include_Select_Count()
5867+
{
5868+
AssertQueryScalar<Gear>(gs => gs.Include(g => g.CityOfBirth).GroupBy(g => g.Rank).Select(g => g.Count()));
5869+
}
5870+
5871+
[ConditionalFact]
5872+
public virtual void GroupBy_Property_Include_Select_LongCount()
5873+
{
5874+
AssertQueryScalar<Gear>(gs => gs.Include(g => g.CityOfBirth).GroupBy(g => g.Rank).Select(g => g.LongCount()));
5875+
}
5876+
5877+
[ConditionalFact]
5878+
public virtual void GroupBy_Property_Include_Select_Max()
5879+
{
5880+
AssertQueryScalar<Gear>(gs => gs.Include(g => g.CityOfBirth).GroupBy(g => g.Rank).Select(g => g.Max(gg => gg.SquadId)));
5881+
}
5882+
5883+
[ConditionalFact]
5884+
public virtual void GroupBy_Property_Include_Select_Min()
5885+
{
5886+
AssertQueryScalar<Gear>(gs => gs.Include(g => g.CityOfBirth).GroupBy(g => g.Rank).Select(g => g.Min(gg => gg.SquadId)));
5887+
}
5888+
5889+
[ConditionalFact]
5890+
public virtual void GroupBy_Property_Include_Aggregate_with_anonymous_selector()
5891+
{
5892+
AssertQuery<Gear>(
5893+
gs =>
5894+
gs.Include(g => g.CityOfBirth).GroupBy(g => g.Nickname).OrderBy(g => g.Key)
5895+
.Select(
5896+
g => new
5897+
{
5898+
g.Key,
5899+
c = g.Count()
5900+
}),
5901+
assertOrder: true);
5902+
}
5903+
5904+
[ConditionalFact(Skip = "issue #12340")]
5905+
public virtual void Group_by_entity_key_with_include_on_that_entity_with_key_in_result_selector()
5906+
{
5907+
AssertQuery<Gear>(
5908+
gs => gs
5909+
.GroupBy(g => g.CityOfBirth)
5910+
.OrderBy(g => g.Key.Name)
5911+
.Select(g => g.Key)
5912+
.Include(c => c.BornGears).ThenInclude(g => g.Weapons),
5913+
assertOrder: true,
5914+
elementAsserter: (e, a) =>
5915+
{
5916+
Assert.Equal(e.Name, a.Name);
5917+
5918+
Assert.Equal(e.BornGears == null, a.BornGears == null);
5919+
Assert.Equal(e.BornGears.Count(), a.BornGears.Count());
5920+
});
5921+
}
5922+
5923+
[ConditionalFact(Skip = "issue #12340")]
5924+
public virtual void Group_by_entity_key_with_include_on_that_entity_with_key_in_result_selector_using_EF_Property()
5925+
{
5926+
AssertQuery<Gear>(
5927+
gs => gs
5928+
.GroupBy(g => g.CityOfBirth)
5929+
.OrderBy(g => g.Key.Name)
5930+
.Select(g => EF.Property<City>(g, "Key"))
5931+
.Include(c => c.BornGears).ThenInclude(g => g.Weapons),
5932+
assertOrder: true,
5933+
elementAsserter: (e, a) =>
5934+
{
5935+
Assert.Equal(e.Name, a.Name);
5936+
5937+
Assert.Equal(e.BornGears == null, a.BornGears == null);
5938+
Assert.Equal(e.BornGears.Count(), a.BornGears.Count());
5939+
});
5940+
}
5941+
5942+
[ConditionalFact]
5943+
public virtual void Group_by_with_include_with_entity_in_result_selector()
5944+
{
5945+
AssertQuery<Gear>(
5946+
gs =>
5947+
gs.Include(g => g.CityOfBirth).GroupBy(g => g.Rank).OrderBy(g => g.Key)
5948+
.Select(
5949+
g => new
5950+
{
5951+
g.Key,
5952+
c = g.Count(),
5953+
element = g.OrderBy(gg => gg.Nickname).FirstOrDefault()
5954+
}),
5955+
assertOrder: true,
5956+
elementAsserter: (e, a) =>
5957+
{
5958+
Assert.Equal(e.Key, a.Key);
5959+
Assert.Equal(e.c, a.c);
5960+
Assert.Equal(e.element.Nickname, a.element.Nickname);
5961+
Assert.Equal(e.element.CityOfBirth == null, a.element.CityOfBirth == null);
5962+
if (e.element.CityOfBirth != null)
5963+
{
5964+
Assert.Equal(e.element.CityOfBirth.Name, a.element.CityOfBirth.Name);
5965+
}
5966+
});
5967+
}
5968+
5969+
[ConditionalFact]
5970+
public virtual void Include_with_group_by_and_FirstOrDefault_gets_properly_applied()
5971+
{
5972+
var expectedIncludes = new List<IExpectedInclude>
5973+
{
5974+
new ExpectedInclude<Gear>(e => e.CityOfBirth, "CityOfBirth"),
5975+
new ExpectedInclude<Officer>(e => e.CityOfBirth, "CityOfBirth"),
5976+
};
5977+
5978+
AssertIncludeQuery<Gear>(
5979+
gs => gs.Include(g => g.CityOfBirth).GroupBy(g => g.Rank).Select(g => g.FirstOrDefault(gg => gg.HasSoulPatch)),
5980+
expectedIncludes);
5981+
}
5982+
58535983
// Remember to add any new tests to Async version of this test class
58545984

58555985
protected GearsOfWarContext CreateContext() => Fixture.CreateContext();

src/EFCore/Query/ExpressionVisitors/Internal/QuerySourceTracingExpressionVisitor.cs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
// Copyright (c) .NET Foundation. All rights reserved.
22
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
33

4+
using System;
5+
using System.Collections.Generic;
46
using System.Linq;
57
using System.Linq.Expressions;
68
using JetBrains.Annotations;
@@ -100,6 +102,16 @@ protected override Expression VisitQuerySourceReference(QuerySourceReferenceExpr
100102
return expression;
101103
}
102104

105+
private static readonly ISet<Type> _aggregateResultOperators = new HashSet<Type>
106+
{
107+
typeof(AverageResultOperator),
108+
typeof(CountResultOperator),
109+
typeof(LongCountResultOperator),
110+
typeof(MaxResultOperator),
111+
typeof(MinResultOperator),
112+
typeof(SumResultOperator)
113+
};
114+
103115
/// <summary>
104116
/// This API supports the Entity Framework Core infrastructure and is not intended to be used
105117
/// directly from your code. This API may change or be removed in future releases.
@@ -111,6 +123,11 @@ protected override Expression VisitSubQuery(SubQueryExpression subQueryExpressio
111123
return subQueryExpression;
112124
}
113125

126+
if (subQueryExpression.QueryModel.ResultOperators.Any(ro => _aggregateResultOperators.Contains(ro.GetType())))
127+
{
128+
return subQueryExpression;
129+
}
130+
114131
_originGroupByQueryModel
115132
= subQueryExpression.QueryModel.ResultOperators
116133
.Any(ro => ro is GroupResultOperator)

test/EFCore.SqlServer.FunctionalTests/Query/GearsOfWarQuerySqlServerTest.cs

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7614,6 +7614,124 @@ public override void Time_of_day_datetimeoffset()
76147614
FROM [Missions] AS [m]");
76157615
}
76167616

7617+
public override void GroupBy_Property_Include_Select_Average()
7618+
{
7619+
base.GroupBy_Property_Include_Select_Average();
7620+
7621+
AssertSql(
7622+
@"SELECT AVG(CAST([g].[SquadId] AS float))
7623+
FROM [Gears] AS [g]
7624+
WHERE [g].[Discriminator] IN (N'Officer', N'Gear')
7625+
GROUP BY [g].[Rank]");
7626+
}
7627+
7628+
public override void GroupBy_Property_Include_Select_Sum()
7629+
{
7630+
base.GroupBy_Property_Include_Select_Sum();
7631+
7632+
AssertSql(
7633+
@"SELECT SUM([g].[SquadId])
7634+
FROM [Gears] AS [g]
7635+
WHERE [g].[Discriminator] IN (N'Officer', N'Gear')
7636+
GROUP BY [g].[Rank]");
7637+
}
7638+
7639+
public override void GroupBy_Property_Include_Select_Count()
7640+
{
7641+
base.GroupBy_Property_Include_Select_Count();
7642+
7643+
AssertSql(
7644+
@"SELECT COUNT(*)
7645+
FROM [Gears] AS [g]
7646+
WHERE [g].[Discriminator] IN (N'Officer', N'Gear')
7647+
GROUP BY [g].[Rank]");
7648+
}
7649+
7650+
public override void GroupBy_Property_Include_Select_LongCount()
7651+
{
7652+
base.GroupBy_Property_Include_Select_LongCount();
7653+
7654+
AssertSql(
7655+
@"SELECT COUNT_BIG(*)
7656+
FROM [Gears] AS [g]
7657+
WHERE [g].[Discriminator] IN (N'Officer', N'Gear')
7658+
GROUP BY [g].[Rank]");
7659+
}
7660+
7661+
public override void GroupBy_Property_Include_Select_Min()
7662+
{
7663+
base.GroupBy_Property_Include_Select_Min();
7664+
7665+
AssertSql(
7666+
@"SELECT MIN([g].[SquadId])
7667+
FROM [Gears] AS [g]
7668+
WHERE [g].[Discriminator] IN (N'Officer', N'Gear')
7669+
GROUP BY [g].[Rank]");
7670+
}
7671+
7672+
public override void GroupBy_Property_Include_Aggregate_with_anonymous_selector()
7673+
{
7674+
base.GroupBy_Property_Include_Aggregate_with_anonymous_selector();
7675+
7676+
AssertSql(
7677+
@"SELECT [g].[Nickname] AS [Key], COUNT(*) AS [c]
7678+
FROM [Gears] AS [g]
7679+
WHERE [g].[Discriminator] IN (N'Officer', N'Gear')
7680+
GROUP BY [g].[Nickname]
7681+
ORDER BY [Key]");
7682+
}
7683+
7684+
public override void Group_by_entity_key_with_include_on_that_entity_with_key_in_result_selector()
7685+
{
7686+
base.Group_by_entity_key_with_include_on_that_entity_with_key_in_result_selector();
7687+
7688+
AssertSql(
7689+
@"");
7690+
}
7691+
7692+
public override void Group_by_entity_key_with_include_on_that_entity_with_key_in_result_selector_using_EF_Property()
7693+
{
7694+
base.Group_by_entity_key_with_include_on_that_entity_with_key_in_result_selector_using_EF_Property();
7695+
7696+
AssertSql(
7697+
@"");
7698+
}
7699+
7700+
public override void Group_by_with_include_with_entity_in_result_selector()
7701+
{
7702+
base.Group_by_with_include_with_entity_in_result_selector();
7703+
7704+
AssertSql(
7705+
@"SELECT [g].[Nickname], [g].[SquadId], [g].[AssignedCityName], [g].[CityOrBirthName], [g].[Discriminator], [g].[FullName], [g].[HasSoulPatch], [g].[LeaderNickname], [g].[LeaderSquadId], [g].[Rank], [g.CityOfBirth].[Name], [g.CityOfBirth].[Location]
7706+
FROM [Gears] AS [g]
7707+
INNER JOIN [Cities] AS [g.CityOfBirth] ON [g].[CityOrBirthName] = [g.CityOfBirth].[Name]
7708+
WHERE [g].[Discriminator] IN (N'Officer', N'Gear')
7709+
ORDER BY [g].[Rank]");
7710+
}
7711+
7712+
public override void GroupBy_Property_Include_Select_Max()
7713+
{
7714+
base.GroupBy_Property_Include_Select_Max();
7715+
7716+
AssertSql(
7717+
@"SELECT MAX([g].[SquadId])
7718+
FROM [Gears] AS [g]
7719+
WHERE [g].[Discriminator] IN (N'Officer', N'Gear')
7720+
GROUP BY [g].[Rank]");
7721+
}
7722+
7723+
public override void Include_with_group_by_and_FirstOrDefault_gets_properly_applied()
7724+
{
7725+
base.Include_with_group_by_and_FirstOrDefault_gets_properly_applied();
7726+
7727+
AssertSql(
7728+
@"SELECT [g].[Nickname], [g].[SquadId], [g].[AssignedCityName], [g].[CityOrBirthName], [g].[Discriminator], [g].[FullName], [g].[HasSoulPatch], [g].[LeaderNickname], [g].[LeaderSquadId], [g].[Rank], [g.CityOfBirth].[Name], [g.CityOfBirth].[Location]
7729+
FROM [Gears] AS [g]
7730+
INNER JOIN [Cities] AS [g.CityOfBirth] ON [g].[CityOrBirthName] = [g.CityOfBirth].[Name]
7731+
WHERE [g].[Discriminator] IN (N'Officer', N'Gear')
7732+
ORDER BY [g].[Rank]");
7733+
}
7734+
76177735
private void AssertSql(params string[] expected)
76187736
=> Fixture.TestSqlLoggerFactory.AssertBaseline(expected);
76197737

0 commit comments

Comments
 (0)