如何在一个查询中获取具有角色的用户?

How to get users with roles in one single query?

我已经按照此处所述设置了我的身份模型: https://docs.microsoft.com/en-us/aspnet/core/security/authentication/customize-identity-model?view=aspnetcore-2.2#add-user-and-role-navigation-properties

所以我的每个用户 class 都有一组角色,通过 UserRole "wrapper"。 所有的实体关系都设置好了。

当我查询我的用户时,我得到了每个用户的所有角色(在这里使用延迟加载,但 "Include" 没有区别):

var users = _userManager.Users
    .AsNoTracking()
    .ToList();

但是在检查 EF Core 创建的日志时,我发现每个用户都有另一个查询来获取角色:

[Parameters=[@_outer_Id='d550f61b-ed3d-4d90-8e7b-31552de50d3b' (Size = 450)], CommandType='"Text"', CommandTimeout='30']
SELECT [r].[RoleId] AS [Id], [r.Role].[Name], [r.Role].[DisplayName]
FROM [AspNetUserRoles] AS [r]
INNER JOIN [AspNetRoles] AS [r.Role] ON [r].[RoleId] = [r.Role].[Id]
WHERE ([r].[Discriminator] = N'UserRole') AND (@_outer_Id = [r].[UserId])

这对我数据库中的每个用户 ID 重复。

怎样才能只用一次查询就得到结果?

以防万一,我的模特们:

public class User : IdentityUser
{
    public virtual ICollection<UserRole> UserRoles { get; set; }
}

public class UserRole : IdentityUserRole<string>
{
    public virtual User User { get; set; }
    public virtual Role Role { get; set; }
}

public class Role : IdentityRole
{
    public virtual ICollection<UserRole> UserRoles { get; set; }
}

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    base.OnModelCreating(modelBuilder);

    modelBuilder.Entity<User>(b =>
    {
        b.HasMany(e => e.UserRoles)
            .WithOne(e => e.User)
            .HasForeignKey(ur => ur.UserId)
            .IsRequired();
    });

    modelBuilder.Entity<Role>(b =>
    {
        b.HasMany(e => e.UserRoles)
            .WithOne(e => e.Role)
            .HasForeignKey(ur => ur.RoleId)
            .IsRequired();
    });
}

您似乎启用了延迟加载。 EF 永远不会自动连接相关表,您必须以某种方式指示它这样做。在延迟加载的情况下,这是通过访问导航 属性 的 getter 发生的。 getter 被 EF 覆盖以从对象缓存中提取相关实体,如果在那里找不到它们,则发出查询以检索它们(因此 "lazy")。您显然只是遍历用户并访问每个用户的 UserRoles 成员,这会导致为每个用户发出单独的查询。

您要做的是急切加载关系。您可以通过 Include(对于子关系 ThenInclude)执行此操作。换句话说:

var users = await _userManager.Users
    .Include(x => x.UserRoles)
        .ThenInclude(x => x.Role)
    .AsNoTracking()
    .ToListAsync();