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

What is the right way to extend AspnetUserRoles table in ASPNET.Core 3.1?

情况

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

默认身份提供:

public virtual TKey UserId { get; set; }
public virtual TKey RoleId { get; set; }

当我将我的 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; }

}

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);
    }
}

实体

public class User : IdentityUser<long> {}
public class Role : IdentityRole<long> {}
public class UserRole : IdentityUserRole<long>
{
    public long CompanyId { get; set; }
}

出现问题

当我注册我的用户并且它 returns 为真时,我将在 UserRole table 中添加一个当前用户,如下所示,但是当我的调试器到达 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();

}

我不知道我哪里做错了?如果上述覆盖用户角色的步骤是错误的,请协助我解决这个问题。

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

迁移

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);
                });

迁移快照

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");
                });

现在我想出了我自己问题的答案。

回忆问题

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

错误 1

Invalid column name 'discriminator'

错误 2

A key cannot be configured on ‘UserRole’ because it is a derived type. The key must be configured on the root type ‘IdentityUserRole’. If you did not intend for ‘IdentityUserRole’ to be included in the model, ensure that it is not included in a DbSet property on your context, referenced in a configuration call to ModelBuilder, or referenced from a navigation property on a type that is included in the model.

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

public virtual TKey UserId { get; set; }
public virtual TKey RoleId { get; set; }

那么,解决办法是什么?

要解决此问题,我们必须重写用户角色实现。

现在,如果您看到代码:

public class CompanyDBContext : IdentityDbContext<User, Role, long, UserClaim, UserRole, UserLogin, RoleClaim, UserToken>, IPersistedGrantDbContext
    {
        private readonly IOptions<OperationalStoreOptions> _operationalStoreOptions;
        public CompanyDBContext(
            DbContextOptions options,
            IOptions<OperationalStoreOptions> operationalStoreOptions) : base(options)
        {
            _operationalStoreOptions = operationalStoreOptions;
        }

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

        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            base.OnModelCreating(modelBuilder);
            modelBuilder.ConfigurePersistedGrantContext(_operationalStoreOptions.Value);
            modelBuilder.Entity<UserRole>(b =>
            {
                b.HasKey(ur => new { ur.UserId, ur.RoleId, ur.CompanyId });
            });
        public DbSet<DeviceFlowCodes> DeviceFlowCodes { get; set; }
        public DbSet<PersistedGrant> PersistedGrants { get; set; }

    }

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

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

public class User : IdentityUser<long> { //Add new props to modify ASP.NETUsers Table }

public class Role : IdentityRole<long> { }

public class UserClaim: IdentityUserClaim<long> { }

public class UserRole : IdentityUserRole<long>
{

    [Key, Column(Order = 2)]
    public long CompanyId { get; set; }

    public Company Company { get; set; }
}

public class UserLogin: IdentityUserLogin<long> { }

public class RoleClaim : IdentityRoleClaim<long> { }

public class UserToken : IdentityUserToken<long> { }

结论

add-migration FirstMigration -context CompanyDbContext
update-database -context CompanyDbContext

快乐编码:)

Ref Link

继承自 IdentityRole

 public class myRoles: IdentityRole
  {
    public bool ACTIVE { get; set; }
    public bool LOCKED { get; set; }
    public int SORT_ORDER { get; set; }
    public bool DEFAULT_FLAG { get; set; }
    public string USER_PRIVILEGES { get; set; }
    public string CREATED_BY { get; set; }
    public DateTime? CREATED_DATE { get; set; }
    public string UPDATED_BY { get; set; }
    public DateTime? UPDATED_DATE { get; set; }

}

在 startUp

中引用您的角色类别
services.AddIdentity<RegisterUser, myRoles>
            .AddEntityFrameworkStores<ContextClass>()