如何同时获取IdentityUsers及其角色的列表?

时间:2018-09-05 21:21:35

标签: c# entity-framework-core roles asp.net-core-identity

聊天消息模型:

public class ChatMessage
{
    [Key] public long Id { get; set; }

    [Required] public string Content { get; set; }
    [Required] public DateTime TimePosted { get; set; }

    public string AuthorId { get; set; }
    [Required, ForeignKey("authorId")] public ApplicationUser Author { get; set; }
}

现在,我想获取某一天的聊天消息列表,同时知道他们的作者是否是“语音”角色:

var messages = await dbContext.ChatMessages.AsNoTracking()
    .Where(chm => chm.TimePosted.Date == someDateTime.Date).OrderBy(chm => chm.TimePosted)
    .ToListAsync();

var result = new List<Tuple<ChatMessage, bool>> { };
foreach(var m in messages)
    result.Add(Tuple.Create(m, await userManager.IsInRoleAsync(Author, "Voice")));

据我所知,它将发送到数据库与要获取的聊天消息一样多的查询,这似乎令人讨厌,因为这只能在一个查询中完成,它?或者至少是恒定数量的查询,但不是线性的。

我做错了吗?如果是,该怎么办?

3 个答案:

答案 0 :(得分:0)

您应该能够IncludeApplicationUserClaims表,并在查询中完成所有操作。

我还没有完全测试过,但是类似:

dbContext.ChatMessages
    .Include(i => i.Author)
    .Include(i => i.Author.Claims)
    .Where(w => w.Author.Claims.Any(a => a.ClaimType == ClaimTypes.Role 
                                         && a.ClaimValue == "Voice"));

答案 1 :(得分:0)

这是我目前拥有的。未测试,但似乎没有给出编译错误:

string voiceRoleId = (await roleManager.FindByNameAsync("Voice")).Id;
var result = await dbContext.ChatMessages.AsNoTracking()
    .Where(chm => chm.TimePosted.Date == someDateTime.Date).OrderBy(chm => chm.TimePosted)
    .GroupJoin(
        dbContext.UserRoles.Where(ur => ur.RoleId == voiceRoleId),
        chm => chm.AuthorId, r => r.UserId,
        (chm, rs) => new { Message = chm, IsVoice = rs.Any() }
    ).ToListAsync();

这太令人惊奇了。我认为该任务将比8行代码简单。

答案 2 :(得分:0)

正确的方法似乎来自@Kirk Larkin的comment,我将其转换为答案,因为它比注释更可见,并且注释随时可能消失。

他链接了此文档页面:Migrate authentication and Identity to ASP.NET Core 2.0#Add IdentityUser POCO navigation properties,其中说:

  

基础的实体框架(EF)核心导航属性   IdentityUser POCO(普通旧CLR对象)已被删除。如果你的   1.x项目使用了这些属性,然后将它们手动添加回2.0项目:C#

/// <summary>
/// Navigation property for the roles this user belongs to.
/// </summary>
public virtual ICollection<IdentityUserRole<int>> Roles { get; } = new List<IdentityUserRole<int>>();

/// <summary>
/// Navigation property for the claims this user possesses.
/// </summary>
public virtual ICollection<IdentityUserClaim<int>> Claims { get; } = new List<IdentityUserClaim<int>>();

/// <summary>
/// Navigation property for this users login accounts.
/// </summary>
public virtual ICollection<IdentityUserLogin<int>> Logins { get; } = new List<IdentityUserLogin<int>>();
     

为防止在运行EF Core Migrations时出现重复的外键,请添加   以下是IdentityDbContext类的OnModelCreating方法   (在base.OnModelCreating()之后;调用):C#

protected override void OnModelCreating(ModelBuilder builder)
{
    base.OnModelCreating(builder);
    // Customize the ASP.NET Core Identity model and override the defaults if needed.
    // For example, you can rename the ASP.NET Core Identity table names and more.
    // Add your customizations after calling base.OnModelCreating(builder);

    builder.Entity<ApplicationUser>()
        .HasMany(e => e.Claims)
        .WithOne()
        .HasForeignKey(e => e.UserId)
        .IsRequired()
        .OnDelete(DeleteBehavior.Cascade);

    builder.Entity<ApplicationUser>()
        .HasMany(e => e.Logins)
        .WithOne()
        .HasForeignKey(e => e.UserId)
        .IsRequired()
        .OnDelete(DeleteBehavior.Cascade);

    builder.Entity<ApplicationUser>()
        .HasMany(e => e.Roles)
        .WithOne()
        .HasForeignKey(e => e.UserId)
        .IsRequired()
        .OnDelete(DeleteBehavior.Cascade);
}

完成这些步骤后,我相信编写正确的查询应该像单个Include语句一样简单。大脑编译的代码如下:

var usersAndRoles = await dbContext.ChatMessages.AsNoTracking()
    .Include(msg => msg.Author).ThenInclude(author => author.Roles)
    .Select(msg => new { Message = msg, IsVoice = msg.Author.Roles.Contains("Voice") })
    .ToListAsync();