EF Core 2 如何在 IdentityUser 上包含角色导航属性?

Posted

技术标签:

【中文标题】EF Core 2 如何在 IdentityUser 上包含角色导航属性?【英文标题】:EF Core 2 How To Include Roles Navigation Property On IdentityUser? 【发布时间】:2018-05-25 19:44:30 【问题描述】:

我正在使用 EF Core 2 设置一个新项目,并且我需要在 IdentityUser 上有一个导航属性,因此当我查询用户时,我可以包含(x => x.Roles)并获取用户的角色在里面。

Github 上的这篇文章有一些想法,但我已经尝试了每一个和所有导致问题的方法,方法是在身份表上创建新的/重复的字段或导致迁移问题。 EF 团队中的任何人都没有发表官方评论。

https://github.com/aspnet/Identity/issues/1361

我想知道是否有人可以正常工作?并且可以分享他们的 EF DB 映射和模型。

【问题讨论】:

.Net Core 2.1 的最新修复解决了我的问题***.com/questions/51004516/… 【参考方案1】:

将我的 10 美分加到已经很出色的 accepted answer 上。请务必记住使您的 DbContext 适应您将要进行的更改。

在接受答案的情况下,您 DbContext 应该看起来类似于:

public class DbContext: IdentityDbContext<User, IdentityRole<int>, string,
        IdentityUserClaim<string>, UserRole, IdentityUserLogin<string>, 
        IdentityRoleClaim<string>, IdentityUserToken<string>>, 
        IDesignTimeDbContextFactory<ApplicationContext>

    // ...Rest of the code goes here

注意UserRole 替换了默认的TUserRole 参数。我花了几个小时才弄清楚这一点,我希望它可以节省其他人的时间。

【讨论】:

在添加迁移时使用此实现,您是否遇到过这样的错误? “实体类型 'IdentityUserLogin' 需要定义一个主键。如果您打算使用无键实体类型调用 'HasNoKey()'。”这很烦人,我几乎尝试了网络上的所有解决方案,但没有运气。 我不记得了。您是否尝试过类似builder.Entity&lt;IdentityUserLogin&lt;string&gt;&gt;().HasKey(x =&gt; x.UserId); 的方法来覆盖OnModelCreating 方法。在这个答案中做了类似的事情 (***.com/a/45679795/8058709) 感谢您的建议,我已经设法以其他方式修复它。我找到了原因,在我的例子中,onModelCreating() 中有一些映射同时使用了 IdentityUserLogin 和 ApplicationUserLogin。似乎 EF 对选择其中一种模型感到困惑。我通过用我的自定义模型(ApplicationUserLogin)替换所有默认(IdentityUserLogin)模型来修复它。这实际上对我有用。【参考方案2】:

请参阅“将身份验证和身份迁移到 ASP.NET Core 2.0”的文档,特别是“Add IdentityUser POCO Navigation Properties”部分:

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

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

为防止在运行 EF Core 迁移时出现重复的外键,请添加 以下是您的IdentityDbContext 班级'OnModelCreating 方法(在base.OnModelCreating(); 调用之后):

protected override void OnModelCreating(ModelBuilder builder)

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

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

编辑

以上内容仅满足通过IdentityUserRole 链接表访问对用户持有的角色ID 的任务。要通过导航属性访问角色实体本身,您需要添加另一个导航属性(这次针对从IdentityUserRole 继承的实体)。请参阅以下步骤:

    修改IdentityUser 实体上的Roles 导航属性,如下所示:
public virtual ICollection<UserRole> Roles  get; set;  = new List<UserRole>();
    创建上面引用的UserRole 实体:
public class UserRole : IdentityUserRole<int>

    public virtual IdentityRole<int> Role  get; set; 

    如下构造UserRole 的映射:
builder.Entity<UserRole>()
    .HasOne(e => e.Role)
    .WithMany()
    .HasForeignKey(e => e.RoleId)
    .IsRequired()
    .OnDelete(DeleteBehavior.Cascade);
    然后您可以检索实体(填充导航属性),如下所示:
User user = context.Set<User>()
    .Include(u => u.Roles)
    .ThenInclude(r => r.Role)
    .FirstOrDefault();

注意:

由于这是加载多对多关系的另一端,这可能会导致对数据库的多次调用(请参阅N+1 problem)。 当您创建一个继承自 IdentityUserRole 的新实体时,您需要迁移或重新创建数据库。 如果您想将此导航属性与UserManagerRoleManager 您将需要使用长格式重载 AddUserStore()AddRoleStore 在你的启动课程中,例如
services.AddIdentity<User, IdentityRole<int>>()
    .AddUserStore<UserStore<User, IdentityRole<int>, SqlContext, int, IdentityUserClaim<int>, UserRole, IdentityUserLogin<int>, IdentityUserToken<int>, IdentityRoleClaim<int>>>()
    .AddRoleStore<RoleStore<IdentityRole<int>, SqlContext, int, UserRole, IdentityRoleClaim<int>>>()
    .AddDefaultTokenProviders();

【讨论】:

感谢您抽出宝贵时间回复并感谢您提供的信息。这只是 UserRoles 而不是角色?不幸的是,如果没有另一个数据库查找来获取角色名称,我仍然没有办法? @leen3o:啊,我现在明白这个问题了。请参阅我的编辑。但是,请注意,它可能无法通过单个数据库调用来实现。 优秀。我希望我能给这个更多的赞成票。谢谢。 不客气 :)。它导致多个数据库调用的事实可能使这有点无用,但认为无论如何都值得添加。 @leen3o 另一个注意事项:由于这会创建一个继承自 IdentityUserRole 的新实体,因此您需要迁移或重新创建数据库。【参考方案3】:

我通过自定义查询获取角色,可能会有所帮助。

var roles = (from role in _dbContext.Roles
                let userRoles = _dbContext.UserRoles.Where(ur => ur.UserId == user.Id).Select(ur => ur.RoleId)
                where userRoles.Contains(role.Id)
                select role
            ).ToList();

【讨论】:

以上是关于EF Core 2 如何在 IdentityUser 上包含角色导航属性?的主要内容,如果未能解决你的问题,请参考以下文章

如何在EF Core中以两种方式制作唯一键?

如何在 EF Core 2.1.0 中播种管理员用户?

EF Core 2.0中如何手动映射数据库的视图为实体

如何在 EF Core 的 Hot Chocolate 中打开和关闭包含

Ef core 如何设置主键

Ef core 如何设置主键