在 ASPNET.Core 3.1 中扩展 AspnetUserRoles 表的正确方法是什么?

Ahm*_*san 2 asp.net-mvc entity-framework entity-framework-core asp.net-core asp.net-core-identity

情况

我正在使用 Identity ASP.NET Core 3.1 和 Angular 8 模板。我想扩展 ASPNETUserRoles 表并在其中添加另一个自定义键列 CompanyId。

默认情况下,身份提供:

public virtual TKey UserId { get; set; }
public virtual TKey RoleId { get; set; }
Run Code Online (Sandbox Code Playgroud)

当我将 DbContext 从 UserId(字符串)修改为 UserId(长整型)时,DbContext 看起来像:

public class CompanyDBContext : KeyApiAuthorizationDbContext<User, Role, UserRole, long>
{
    public CompanyDBContext(
        DbContextOptions options,
        IOptions<OperationalStoreOptions> operationalStoreOptions) : base(options, operationalStoreOptions)
        {
        }
        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            base.OnModelCreating(modelBuilder);
        }
    public DbSet<Company> Companies { get; set; }

}
Run Code Online (Sandbox Code Playgroud)

KeyApiAuthorizationDbContext

public class KeyApiAuthorizationDbContext<TUser, TRole, IdentityUserRole, TKey> : IdentityDbContext<TUser, TRole, TKey, IdentityUserClaim<TKey>, IdentityUserRole<TKey>, IdentityUserLogin<TKey>, IdentityRoleClaim<TKey>, IdentityUserToken<TKey>>, IPersistedGrantDbContext
    where TUser : IdentityUser<TKey>
    where TRole : IdentityRole<TKey>
    where IdentityUserRole : IdentityUserRole<TKey>
    where TKey : IEquatable<TKey>
{
    private readonly IOptions<OperationalStoreOptions> _operationalStoreOptions;

    /// <summary>
    /// Initializes a new instance of <see cref="ApiAuthorizationDbContext{TUser, TRole, TKey}"/>.
    /// </summary>
    /// <param name="options">The <see cref="DbContextOptions"/>.</param>
    /// <param name="operationalStoreOptions">The <see cref="IOptions{OperationalStoreOptions}"/>.</param>
    public KeyApiAuthorizationDbContext(
        DbContextOptions options,
        IOptions<OperationalStoreOptions> operationalStoreOptions)
        : base(options)
    {
        _operationalStoreOptions = operationalStoreOptions;
    }

    /// <summary>
    /// Gets or sets the <see cref="DbSet{PersistedGrant}"/>.
    /// </summary>
    public DbSet<PersistedGrant> PersistedGrants { get; set; }

    /// <summary>
    /// Gets or sets the <see cref="DbSet{DeviceFlowCodes}"/>.
    /// </summary>
    public DbSet<DeviceFlowCodes> DeviceFlowCodes { get; set; }

    Task<int> IPersistedGrantDbContext.SaveChangesAsync() => base.SaveChangesAsync();

    /// <inheritdoc />
    protected override void OnModelCreating(ModelBuilder builder)
    {
        base.OnModelCreating(builder);
        builder.ConfigurePersistedGrantContext(_operationalStoreOptions.Value);
    }
}
Run Code Online (Sandbox Code Playgroud)

实体

public class User : IdentityUser<long> {}
public class Role : IdentityRole<long> {}
public class UserRole : IdentityUserRole<long>
{
    public long CompanyId { get; set; }
}
Run Code Online (Sandbox Code Playgroud)

出现问题

当我注册我的用户并且它返回 true 时,我将在 UserRole 表中添加当前用户,如下所示,但是当我的调试器到达await _context.SaveChangesAsync();方法时,它会显示一个异常

if (result.Succeeded)
{
    foreach (var role in model.Roles.Where(x => x.IsChecked = true))
    {
        var entity = new Core.Entities.Identity.UserRole()
        {
            UserId = model.User.Id,
            RoleId = role.Id,
            CompanyId = companycode
        };

            _context.UserRoles.Add(entity);
    }
    await _context.SaveChangesAsync();

}
Run Code Online (Sandbox Code Playgroud)

在此输入图像描述

我不知道我的错误在哪里?如果上述覆盖用户角色的步骤是错误的,请帮助我。

我还分享了我的迁移详细信息,以供您参考,这可能是我做错的事情。

迁移

migrationBuilder.CreateTable(
name: "Companies",
columns: table => new
{
   Id = table.Column<long>(nullable: false)
       .Annotation("SqlServer:Identity", "1, 1"),
                Name = table.Column<string>(nullable: false),
                Code = table.Column<string>(nullable: false),
                Logo = table.Column<string>(nullable: true)
       },
       constraints: table =>
       {
                table.PrimaryKey("PK_Companies", x => x.Id);
});


migrationBuilder.CreateTable(
                name: "AspNetUserRoles",
                columns: table => new
                {
                    UserId = table.Column<long>(nullable: false),
                    RoleId = table.Column<long>(nullable: false),
                    CompanyId = table.Column<long>(nullable: false)
                },
                constraints: table =>
                {
                    table.PrimaryKey("PK_AspNetUserRoles", x => new { x.UserId, x.RoleId, x.CompanyId });
                    table.ForeignKey(
                        name: "FK_AspNetUserRoles_AspNetRoles_RoleId",
                        column: x => x.RoleId,
                        principalTable: "AspNetRoles",
                        principalColumn: "Id",
                        onDelete: ReferentialAction.Cascade);
                    table.ForeignKey(
                        name: "FK_AspNetUserRoles_AspNetUsers_UserId",
                        column: x => x.UserId,
                        principalTable: "AspNetUsers",
                        principalColumn: "Id",
                        onDelete: ReferentialAction.Cascade);
                    table.ForeignKey(
                        name: "FK_AspNetUserRoles_Companies_CompanyId",
                        column: x => x.CompanyId,
                        principalTable: "Companies",
                        principalColumn: "Id",
                        onDelete: ReferentialAction.Cascade);
                });
Run Code Online (Sandbox Code Playgroud)

迁移快照

modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<long>", b =>
                {
                    b.Property<int>("Id")
                        .ValueGeneratedOnAdd()
                        .HasColumnType("int")
                        .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);

                    b.Property<string>("ClaimType")
                        .HasColumnType("nvarchar(max)");

                    b.Property<string>("ClaimValue")
                        .HasColumnType("nvarchar(max)");

                    b.Property<long>("RoleId")
                        .HasColumnType("bigint");

                    b.HasKey("Id");

                    b.HasIndex("RoleId");

                    b.ToTable("AspNetRoleClaims");
                });
Run Code Online (Sandbox Code Playgroud)

Ahm*_*san 7

现在我对自己的问题有了答案。

\n\n

回忆问题

\n\n

我想扩展用户角色(IdentityUserRole)并添加一些外键,但遇到两个错误:

\n\n

错误1

\n\n
\n

无效的列名称“鉴别器”

\n
\n\n

错误2

\n\n
\n

无法在 \xe2\x80\x98UserRole\xe2\x80\x99 上配置密钥,因为它是派生类型。必须在根类型 \xe2\x80\x98IdentityUserRole\xe2\x80\x99 上配置密钥。如果您不打算将 \xe2\x80\x98IdentityUserRole\xe2\x80\x99 包含在模型中,请确保它不包含在上下文的 DbSet 属性中、在对 ModelBuilder 的配置调用中引用或从模型中包含的类型的导航属性。

\n
\n\n

这是因为如果你查看 IdentityUserRole 的定义,你\xe2\x80\x99会发现它已经有主键:

\n\n
public virtual TKey UserId { get; set; }\npublic virtual TKey RoleId { get; set; }\n
Run Code Online (Sandbox Code Playgroud)\n\n

那么,解决办法是什么?

\n\n

为了解决这个问题,我们必须重写用户角色实现。

\n\n

现在,如果您看到代码:

\n\n
public class CompanyDBContext : IdentityDbContext<User, Role, long, UserClaim, UserRole, UserLogin, RoleClaim, UserToken>, IPersistedGrantDbContext\n    {\n        private readonly IOptions<OperationalStoreOptions> _operationalStoreOptions;\n        public CompanyDBContext(\n            DbContextOptions options,\n            IOptions<OperationalStoreOptions> operationalStoreOptions) : base(options)\n        {\n            _operationalStoreOptions = operationalStoreOptions;\n        }\n\n        Task<int> IPersistedGrantDbContext.SaveChangesAsync() => base.SaveChangesAsync();\n\n        protected override void OnModelCreating(ModelBuilder modelBuilder)\n        {\n            base.OnModelCreating(modelBuilder);\n            modelBuilder.ConfigurePersistedGrantContext(_operationalStoreOptions.Value);\n            modelBuilder.Entity<UserRole>(b =>\n            {\n                b.HasKey(ur => new { ur.UserId, ur.RoleId, ur.CompanyId });\n            });\n        public DbSet<DeviceFlowCodes> DeviceFlowCodes { get; set; }\n        public DbSet<PersistedGrant> PersistedGrants { get; set; }\n\n    }\n
Run Code Online (Sandbox Code Playgroud)\n\n

就是这样。正如 @EduCielo 建议我重新考虑我的代码并进行正确的映射和配置并覆盖模型构建器以映射用户角色的正确关系。

\n\n

对于新开发人员,我正在共享上面使用的实体类,以使其完全适用于您的代码。

\n\n
public class User : IdentityUser<long> { //Add new props to modify ASP.NETUsers Table }\n\npublic class Role : IdentityRole<long> { }\n\npublic class UserClaim: IdentityUserClaim<long> { }\n\npublic class UserRole : IdentityUserRole<long>\n{\n\n    [Key, Column(Order = 2)]\n    public long CompanyId { get; set; }\n\n    public Company Company { get; set; }\n}\n\npublic class UserLogin: IdentityUserLogin<long> { }\n\npublic class RoleClaim : IdentityRoleClaim<long> { }\n\npublic class UserToken : IdentityUserToken<long> { }\n
Run Code Online (Sandbox Code Playgroud)\n\n

结论

\n\n
add-migration FirstMigration -context CompanyDbContext\nupdate-database -context CompanyDbContext\n
Run Code Online (Sandbox Code Playgroud)\n\n

快乐编码:)

\n\n

参考链接

\n