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