EF Core中的SQL Server有效小计

时间:2019-01-23 17:18:37

标签: sql-server entity-framework tsql entity-framework-core

我正在尝试实现类似于以下的查询:

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吗?

2 个答案:

答案 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;来禁用专门用于此操作的延迟加载