聊天消息模型:
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")));
据我所知,这将向数据库发送与查询聊天消息一样多的查询,这似乎是令人讨厌的,因为这应该只在一个查询中完成,不应该吗?或者至少是一定数量的查询,但不是线性的。
我做错了吗?如果是的话,我该怎么做?
您应该能够Include
ApplicationUser
和Claims
表,并在查询中执行此操作。
我没有对此进行过全面测试,但有些内容如下:
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"));
这就是我现在拥有的。 UNTESTED,但似乎没有给出编译错误:
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行代码简单。
正确的方法似乎来自@Kirk Larkin的comment,我正在转换为答案,因为它比评论更明显,因为评论可能随时消失。
他链接了这个文档页面:Migrate authentication and Identity to ASP.NET Core 2.0#Add IdentityUser POCO navigation properties,其中说:
已删除基本IdentityUser POCO(Plain Old CLR Object)的Entity Framework(EF)Core导航属性。如果您的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();