88

I have the following Entity Framework Core 3.0 query:

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));

I get the following error:

Client side GroupBy is not supported.

To run the query on the client, or part of it, I would do the following:

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));

Now it works.

Why am I getting this error if I am not running the query on the client?

4

6 回答 6

204

GroupBy对于 LINQ的功能和 SQL的功能,似乎存在一种常见的误解GROUP BY。由于我最近陷入了完全相同的陷阱并且不得不绕开这个问题,所以我决定对这个问题写一个更彻底的解释。


简短的回答:

LINQ与 SQL语句GroupBy很大不同:LINQ 只是根据键将底层集合划分为块,而 SQL 还应用聚合函数将这些块中的每一个压缩为单个值GROUP BY

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

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

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


长答案:

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

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

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

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

在我们的示例中,我们可能想要获得一个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# 数据类型表示,因为我们可以嵌套任意List深度Dictionary

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

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

据我所知,这些方法都没有在实践中实施。最多有一些 hacky 变通办法,比如 MySQL 的GROUP_CONCAT,它允许将结果行组合成一个字符串(相关的 SO 答案)。

因此我们看到,SQL不能产生与 LINQ 的GroupBy.

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

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 查询使用哪个聚合函数。


总之,LINQGroupBy函数比 SQLGROUP BY语句更通用,由于 SQL 的限制,它只允许返回单个二维结果表。因此,在下载 SQL 结果集后,必须在内存中评估问题中的查询和此答案中的第一个示例。

在 EF Core 3.0 中,开发人员选择在这种情况下抛出异常,而不是隐式执行此操作;这可以防止意外下载具有数百万行的整个可能很大的表,由于测试数据库较小,在开发过程中可能会被忽视。

于 2020-03-20T17:13:04.230 回答
41

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

这是 EF Core 3.0 中最大的突破性变化。 https://docs.microsoft.com/en-us/ef/core/what-is-new/ef-core-3.0/break-changes

于 2019-10-08T05:34:10.953 回答
-1

一种可能的解决方案(对我有用)是制作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);
于 2019-10-24T07:51:22.457 回答
-1

支持客户端分组依据

使用 EF Core 3.1.15.0 测试

以下代码返回Client side GroupBy is not supported.错误:

MyEntity
    .GroupBy(x => x.MyProperty)
    .ToDictionaryAsync(x => x.Key, x => x.Count())
    .Dump();

但是由于某种原因,您可以在.Select()之后添加一个.GroupBy(),它会编译并运行预期的 SQL:

MyEntity
    .GroupBy(x => x.MyProperty)
    .Select(g => new { Key = g.Key, Count = g.Count() })
    .ToDictionaryAsync(x => x.Key, x => x.Count)
    .Dump();

编译为:

SELECT [t].[MyProperty] AS [Key], COUNT(*) AS [Count]
FROM [dbo].[MyEntity] AS [t]
GROUP BY [t].[MyProperty]

来源:https ://stackoverflow.com/a/11564436/14565661

于 2021-10-29T19:33:02.013 回答
-2
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();

你可以试试这个代码部分......它会工作..我试过了

于 2020-11-04T12:44:14.483 回答
-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

于 2020-03-19T00:08:09.987 回答