不支持客户端GroupBy

时间:2019-09-27 16:43:53

标签: entity-framework-core-3.0

我有以下Entity Framework Core 3.0:

var units = await context.Units
  .SelectMany(y => y.UnitsI18N)
  .OrderBy(y => y.Name)
  .GroupBy(y => y.LanguageCode)
  .ToDictionaryAsync(y => y.Key, y => y.Select(z => z.Name));

然后出现以下错误:

Client side GroupBy is not supported.

如果未在客户端上运行查询,为什么会出现此错误?

要在客户端(或其一部分)上运行查询,我将执行以下操作:

var units = context.Units
  .SelectMany(y => y.UnitsI18N)
  .OrderBy(y => y.Name)
  .AsEnumerable()
  .GroupBy(y => y.LanguageCode)
  .ToDictionary(y => y.Key, y => y.Select(z => z.Name));

现在可以使用了...

5 个答案:

答案 0 :(得分:119)

对于LINQ GroupBy的功能和SQL GROUP BY的功能似乎有一个普遍的误解。由于我陷入了完全相同的陷阱,并且不得不在最近陷入困境,因此我决定为这个问题写一个更详尽的解释。


简短答案:

LINQ GroupBy与SQL GROUP BY语句有很大不同:LINQ只是根据密钥将基础集合划分为,而SQL另外应用了一个聚合函数,以将每个这些块压缩为一个单个值

这就是为什么EF必须在内存中执行LINQ类型的GroupBy

在EF Core 3.0之前,这是隐式完成的,因此EF下载了所有结果行,然后应用了LINQ GroupBy。但是,这种隐式行为可能会让程序员期望 entire LINQ查询是在SQL中执行的,当结果集很大时,可能会对性能产生巨大影响。因此,GroupBy的客户端隐式评估为disabled completely in EF Core 3.0

现在,需要显式调用.AsEnumerable().ToList()之类的函数,这些函数将下载结果集并继续进行内存中的LINQ操作。


详细答案:

下表solvedExercises将是此答案的运行示例:

+-----------+------------+
| StudentId | ExerciseId |
+-----------+------------+
|         1 |          1 |
|         1 |          2 |
|         2 |          2 |
|         3 |          1 |
|         3 |          2 |
|         3 |          3 |
+-----------+------------+

此表中的记录X | Y表示学生X已完成练习Y

在该问题中,描述了LINQ的GroupBy方法的常见用例:获取一个集合并将其分组为块,其中每个块中的行共享一个公共密钥。

在我们的示例中,我们可能想获得一个Dictionary<int, List<int>>,其中包含每个学生已解决的练习的列表。使用LINQ,这非常简单:

var result = solvedExercises
    .GroupBy(e => e.StudentId)
    .ToDictionary(e => e.Key, e => e.Select(e2 => e2.ExerciseId).ToList());

输出(有关完整代码,请参见dotnetfiddle):

Student #1: 1 2 
Student #2: 2 
Student #3: 1 2 3 

这很容易用C#数据类型表示,因为我们可以将ListDictionary嵌套到所需的深度。

现在,我们尝试将其想象为一个SQL查询结果。 SQL查询结果通常表示为一个表,我们可以在其中自由选择返回的列。要将以上查询表示为SQL查询结果,我们需要

  • 生成多个结果表,
  • 将分组的行放入数组或
  • 以某种方式插入“结果集分隔符”。

据我所知,这些方法都没有在实践中实施。最多,这里有一些棘手的变通办法,例如MySQL的GROUP_CONCAT,它可以将结果行合并为字符串(relevant SO answer)。

因此,我们看到SQL 无法产生的结果与LINQ的GroupBy概念相匹配。

相反,SQL只允许所谓的 aggregation :例如,如果我们想计算一个学生通过了多少次练习,我们会写

SELECT StudentId,COUNT(ExerciseId)
FROM solvedExercises
GROUP BY StudentId

...将会产生

+-----------+-------------------+
| StudentId | COUNT(ExerciseId) |
+-----------+-------------------+
|         1 |                 2 |
|         2 |                 1 |
|         3 |                 3 |
+-----------+-------------------+

聚合函数将一组行简化为单个值,通常为标量。例如行数,总和,最大值,最小值和平均值。

由EF Core:执行

var result = solvedExercises
    .GroupBy(e => e.StudentId)
    .Select(e => new { e.Key, Count = e.Count() })
    .ToDictionary(e => e.Key, e => e.Count);

生成上述SQL。注意Select,它告诉EF它应该对生成的SQL查询使用哪个聚合函数


总而言之,LINQ GroupBy函数比SQL GROUP BY语句更具通用性,由于SQL的限制,该语句仅允许返回一个二维结果表。因此,在下载SQL结果集之后,必须在内存中评估问题中的查询和该答案中的第一个示例之类的查询。

在EF Core 3.0中,开发人员chose to throw an exception in this case代替了隐式进行此操作;这样可以防止意外下载具有数百万行的整个可能很大的表,由于测试数据库较小,在开发过程中可能不会引起人们的注意。

答案 1 :(得分:9)

您的.GroupBy(y => y.LanguageCode).ToDictionaryAsync(y => y.Key, y => y.Select(z => z.Name));无法转换为SQL。 EF Core 3.0会引发异常,以确保您知道Units中的所有记录将在分组并映射到Dictionary之前从数据库中获取。

这是EF Core 3.0中的重大突破。 https://docs.microsoft.com/en-us/ef/core/what-is-new/ef-core-3.0/breaking-changes

答案 2 :(得分:0)

显然,EF Core 3.0尚不支持它,也就是说,不支持通过SQL执行GroupBy

一种可行的解决方案(对我有用)是使GroupBy对象上的List

var units = (
  await context.Units
  .SelectMany(y => y.UnitsI18N)
  .GroupBy(y => y.LanguageCode)
  .ToDictionaryAsync(y => y.Key, y => y.Select(z => z.Name))
  ).ToList().OrderBy(y => y.Name);

答案 3 :(得分:-1)

var test = unitOfWork.PostCategory.GetAll().Include(u=>u.category).GroupBy(g => g.category.name).Select(s => new
                {

                    name = s.Key,
                    count = s.Count()

                }).OrderBy(o=>o.count).ToList();

您可以尝试此代码部分...它将起作用..我已经尝试过

答案 4 :(得分:-3)

linq GroupBy方法可以完成数据库查询无法完成的工作。这就是linq抛出异常的原因。这不是一个缺少的功能,但是在旧版本的linq中,它只是枚举整个表,然后在本地运行GroupBy。

Linq查询语法碰巧有一个group关键字,可以将其转换为数据库查询。

这是一个使用查询语法在数据库上运行查询的最有效的示例:

var kvPairs = from y in context.Units
              from u in y.UnitsI18N
              orderby u.Name
              group u by u.LanguageCode into g
              select new KeyValuePair<string,IEnumerable<string>>(g.Key, g.Select(z => z.Name));

return new Dictionary<string,IEnumerable<string>>>(kvPairs);

有关更多信息,请参见Microsoft的这篇文章:https://docs.microsoft.com/en-us/ef/core/querying/complex-query-operators#groupby