不支持客户端GroupBy

Mig*_*ura 10 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));
Run Code Online (Sandbox Code Playgroud)

我得到以下错误:

Client side GroupBy is not supported.
Run Code Online (Sandbox Code Playgroud)

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

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

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));
Run Code Online (Sandbox Code Playgroud)

现在可以了...

Jan*_*ann 179

似乎对 LINQGroupBy能做什么和 SQLGROUP BY能做什么有一个普遍的误解。由于我最近陷入了完全相同的陷阱并且不得不围绕这个问题进行处理,因此我决定对这个问题写一个更彻底的解释。


简短的回答:

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

这就是 EF 必须GroupBy在内存中执行 LINQ 类型的原因。

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

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


长答案:

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

+-----------+------------+
| StudentId | ExerciseId |
+-----------+------------+
|         1 |          1 |
|         1 |          2 |
|         2 |          2 |
|         3 |          1 |
|         3 |          2 |
|         3 |          3 |
+-----------+------------+
Run Code Online (Sandbox Code Playgroud)

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());
Run Code Online (Sandbox Code Playgroud)

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

Student #1: 1 2 
Student #2: 2 
Student #3: 1 2 3 
Run Code Online (Sandbox Code Playgroud)

这是很容易用C#的数据类型来表示,因为我们可以嵌套ListDictionary深,因为我们喜欢。

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

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

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

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

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

+-----------+------------+
| StudentId | ExerciseId |
+-----------+------------+
|         1 |          1 |
|         1 |          2 |
|         2 |          2 |
|         3 |          1 |
|         3 |          2 |
|         3 |          3 |
+-----------+------------+
Run Code Online (Sandbox Code Playgroud)

...这将产生

+-----------+-------------------+
| StudentId | COUNT(ExerciseId) |
+-----------+-------------------+
|         1 |                 2 |
|         2 |                 1 |
|         3 |                 3 |
+-----------+-------------------+
Run Code Online (Sandbox Code Playgroud)

聚合函数将一组行减少为单个值,通常是一个标量。示例是行计数、总和、最大值、最小值和平均值。

由 EF Core 实现的:执行

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

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


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

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

  • 这个答案比公认的答案更有帮助和信息丰富。 (28认同)
  • 起初我觉得他们改变它很奇怪,但从你的描述来看,这让我想知道我以前的“工作”查询是否实际上是在客户端执行的。很好的解释。 (7认同)
  • 我同意上面的说法,但缺少一个好的解决方案。 (3认同)
  • 保证每 10 个开发人员中有 9 个在遇到问题之前不知道像本文这样的低级细节(很可能是由于 .NET Core 版本升级 &gt; 2.1)。MS 没有将此免责声明放在其文档中的大写红色文本中,这非常“具有欺骗性”。相反,他们抛出异常 smh (2认同)

小智 8

.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/zh-cn/ef/core/what-is-new/ef-core-3.0/breaking-changes

  • 你的回答很好,但是 EF Core 认真吗?我的表有数百万个条目,我不会首先在客户端上加载它们。没有解决办法吗? (10认同)
  • @jsgoupil 这意味着底层数据库提供程序无法在数据库端执行您的查询。在 EF 3 之前,这只会生成警告,并且您的查询将使用 LINQ-to-Objects 在客户端中执行。从 EF 3 开始,它们抛出异常以确保您知道数据库提供程序无法在数据库端执行查询。请参阅 [EF Core 版本 3 重大更改](https://docs.microsoft.com/en-us/ef/core/what-is-new/ef-core-3.0/writing-changes#linq-queries-不再在客户端上进行评估)。 (2认同)