我正在尝试实现类似于以下的查询:
SELECT r.*, (SELECT COUNT(UserID) FROM RoleUsers ru WHERE ru.RoleId = r.Id) AS Assignments
FROM Roles r
获取每个角色的用户数。
实现所需输出的最简单,最直接的选项:
this.DbContext.Set<Role>().Include(x => x.RoleUser)
.Select(x => new { x, Assignments = x.RoleUsers.Count() });
检索所有角色,然后进行N个查询以检索计数:
SELECT COUNT(*)
FROM [dbo].[RoleUsers] AS [r0]
WHERE @_outer_Id = [r0].[RoleId]
这根本不是一个选择。我也尝试过使用GroupJoin
,但它会在一个查询中加载所有必需的数据集并在内存中执行分组:
this.DbContext.Set<Role>().GroupJoin(this.DbContext.Set<RoleUser>(), role => role.Id,
roleUser => roleUser.RoleId, (role, roleUser) => new
{
Role = role,
Assignments = roleUser.Count()
});
生成的查询:
SELECT [role].[Id], [role].[CustomerId], [role].[CreateDate], [role].[Description], [role].[Mask], [role].[ModifyDate], [role].[Name], [assignment].[UserId], [assignment].[CustomerId], [assignment].[RoleId]
FROM [dbo].[Roles] AS [role]
LEFT JOIN [dbo].[RoleUser] AS [assignment] ON [role].[Id] = [assignment].[RoleId]
ORDER BY [role].[Id]
此外,我正在研究一种使用窗口函数的方法,该函数可以按分区划分计数并使用不同的角色,但是我不知道如何在EF中连接窗口函数:
SELECT DISTINCT r.*, COUNT(ra.UserID) OVER(PARTITION BY ru.RoleId)
FROM RoleUsers ru
RIGHT JOIN Roles r ON r.Id = ru.RoleId
那么,有什么方法可以避免EntitySQL吗?
答案 0 :(得分:3)
当查询投影包含一个完整的实体(例如,
)时,EF Core查询聚合到SQL的转换存在缺陷。.Select(role => new { Role = role, ...}
我知道的唯一解决方法是将项目投影到新实体(至少由EF Core支持)
var query = this.DbContext.Set<Role>()
.Select(role => new
{
Role = new Role { Id = role.Id, Name = role.Name, /* all other Role properies */ },
Assignments = role.RoleUsers.Count()
});
这将转换为单个SQL查询。缺点是您必须手动投影所有实体属性。
答案 1 :(得分:2)
this.DbContext.Set<Role>()
.Select(x => new { x, Assignments = x.RoleUsers.Count() });
由于使用的是Select语句,因此不需要为RoleUser
添加include。此外,我猜您正在使用LazyLoading
,这是预期的行为。如果使用急切加载,则LINQ的结果将在一个查询中运行。
您可以在LINQ查询之前使用context.Configuration.LazyLoadingEnabled = false;
来禁用专门用于此操作的延迟加载