>我使用 EFCore 5.0.12
我有以下实体:
public class Course {
public int Id { get; set; }
[Required]
public string Title { get; set; }
public string Description { get; set; }
public CourseLevel Level { get; set; }
[Column(TypeName = "decimal(7,2)")]
public decimal FullPrice { get; set; }
}
现在我尝试执行如下组查询:
var query3 =
from c in context.Courses
group c by c.Level
into g
select g;
foreach (var group in query3) {
Console.WriteLine($"{group.Key} ({group.Count()})");
foreach (var c in group) {
Console.WriteLine($"t{c.Title}");
}
}
这将生成异常
无法转换给定的"分组依据"模式。调用"可枚举" 在"GroupBy"之前,以在客户端对其进行评估。
为什么不能将其转换为sql?
数据库根本无法产生由普通 LINQGroupBy
生成的结果 .
数据库和 LINQ 都使用不同形式的分组。
与 LINQ 分组相反,数据库有更严格的规则:分组必须始终与聚合函数一起使用,如Count()
、Sum()
、Avg()
、Min()
或Max()
。
示例
给定是下表
Users
-------------------------
| age | name | lastname |
-------------------------
| 27 | Nancy | Doe
| 27 | Nancy | Apple
| 65 | Bob | Uncle
| 27 | Bob | Doe
| 35 | Bob | Marley
| 27 | Jim | Beam
--------------------------
常见的 LINQ 查询是:
"获取按age
分组的所有Users
记录"。
Users.GroupBy(user => user.Age);
结果是一组元首,其中key
是age
,value
是User.Age
等于(key, {...})
的User
实例的集合:
(27, {User, User, User, User})
(65, {User})
(35, {User})
上面的结果显示了数据库分组的局限性:不可能使数据库返回这样的结果。数据库不能返回列表或数组 - 它只返回行和列。因此,此类查询无法转换为 SQL 或由数据库执行,因此 EF 会引发异常以通知客户端此不便。
此问题的解决方案是在数据库上下文之外的客户端显式执行分组,或修复表达式以满足上述有效数据库分组规则。
有效的 SQL 查询为:
"获取按name
分组的age
'27'的用户数。">
SELECT Count(age), name FROM Users WHERE age=27 GROUP BY name
结果是一组元组,其中key
是具有相同年龄的用户的count
,value
这些用户的name
(age_count, name)
:
(2, Nancy)
(1, Bob)
(1, Jim)
如果 LINQ 查询也使用聚合函数,则可以毫无问题地将查询转换为 SQL。 为了生成上述 SQL 查询结果,我们可以编写:
dbContext.Users
.GroupBy(
user => user.Name,
(name, users) => new { Count = users.Count(user => user.Age == 27), name });
或
from user in dbContext.Users
group user by user.Name
into groups
select new { Count = groups.Count(user => user.Age == 27), groups.Key };
你不能直接循环IGrouping
(当然,如果你正在构建一个IQueryable
),文档解释说:
此外,
IGrouping
实现了IEnumerable<TElement>
,这意味着您可以在分组后使用任何 LINQ 运算符对其进行撰写。因为没有数据库结构可以表示IGrouping
,GroupBy
算子在大多数情况下没有翻译。当聚合运算符应用于每个返回标量的组时,可以将其转换为关系数据库中的 SQLGROUP BY
。