c# - AspNet Identity - 重复外键的自定义 IdentityRoleClaim 和 IdentityUserRole 和 IdentityUserClaim

标签 c# asp.net-core entity-framework-core asp.net-identity ef-fluent-api

目前,我使用AspNetCore,配置dbcontext如下:

public class AppDbContext : IdentityDbContext<ApplicationUser, ApplicationRole, string, ApplicationUserClaim, ApplicationUserRole, IdentityUserLogin<string>, ApplicationRoleClaim, IdentityUserToken<string>>
{
    public AppDbContext(DbContextOptions<AppDbContext> options)
        : base(options)
    { }

所以我想自定义 AspNetIdentity,但为了简单起见,我刚刚在表之间配置了外键:

  • ApplicationUser 派生自 IdentityUser
  • ApplicationRole 派生自 IdentityRole
  • ApplicationUserClaim 源自 IdentityUserClaim <string>
  • ApplicationUserRole 派生自 IdentityUserRole <string>
  • ApplicationRoleClaim 源自 IdentityRoleClaim <string>

public class ApplicationUser : IdentityUser
{
    public virtual ICollection<ApplicationUserRole> UserRoles { get; set; }
    public virtual ICollection<ApplicationUserClaim> UserClaims { get; set; }
}

public class ApplicationRole : IdentityRole
{
     public virtual ICollection<ApplicationUserRole> UserRoles { get; set; }
     public virtual ICollection<ApplicationRoleClaim> RoleClaims { get; set; }
}

public class ApplicationRoleClaim : IdentityRoleClaim<string>
{
     public virtual ApplicationRole Role { get; set; }
}

public class ApplicationUserRole : IdentityUserRole<string>
{
     public int Id { get; set; }

     public virtual ApplicationRole Role { get; set; }
     public virtual ApplicationUser User { get; set; }
}

public class ApplicationUserClaim : IdentityUserClaim<string>
{
    public virtual ApplicationUser User { get; set; }
}

下面是派生类实现特定自定义身份模型的 IEntityTypeConfiguration。

public class ApplicationUserEntityBuilder : IEntityTypeConfiguration<ApplicationUser>
{
    public void Configure(EntityTypeBuilder<ApplicationUser> builder)
    {
        // still use default table name
        builder.ToTable("AspNetUsers");

        builder.Property(p => p.Id)
               .HasColumnType("CHAR(36)");
    }
}

public class ApplicationRoleEntityBuilder : IEntityTypeConfiguration<ApplicationRole>
 {
     public void Configure(EntityTypeBuilder<ApplicationRole> builder)
     {
         // still use default table name
         builder.ToTable("AspNetRoles");
     }

 }

 public class ApplicationUserClaimEntityBuilder : IEntityTypeConfiguration<ApplicationUserClaim>
  {
        public void Configure(EntityTypeBuilder<ApplicationUserClaim> builder)
        {
            builder.Property(p => p.UserId)
                   .HasColumnName(nameof(ApplicationUserClaim.UserId))
                   .HasColumnType("CHAR(36)");

            builder.HasOne(p => p.User)
                   .WithMany(u => u.UserClaims)
                   .HasForeignKey(p => p.UserId)
                   .IsRequired();
        }

    }

public class ApplicationUserRoleEntityBuilder : IEntityTypeConfiguration<ApplicationUserRole>
{
    public void Configure(EntityTypeBuilder<ApplicationUserRole> builder)
    {

        // still use default table name
        builder.ToTable("AspNetUserRoles");

        builder.HasKey(p => p.Id);
        builder.Property(p => p.Id)
               .ValueGeneratedOnAdd();

        builder.HasOne(p => p.Role)
               .WithMany(r => r.UserRoles)
               .HasForeignKey(p => p.RoleId)
               .IsRequired();

        builder.HasOne(p => p.User)
               .WithMany(r => r.UserRoles)
               .HasForeignKey(p => p.UserId)
               .IsRequired();
    }
}

public class ApplicationRoleClaimEntityBuilder : IEntityTypeConfiguration<ApplicationRoleClaim>
{
    public void Configure(EntityTypeBuilder<ApplicationRoleClaim> builder)
    {
        // still use default table name
        builder.ToTable("AspNetRoleClaims");

        builder.HasKey(p => p.Id);

        builder.Property(p => p.Id)
               .ValueGeneratedOnAdd();

        builder.Property(p => p.RoleId)
               .HasColumnName(nameof(ApplicationRoleClaim.RoleId))
               .HasColumnType("CHAR(36)");

        builder.HasOne(p => p.Role)
               .WithMany(r => r.RoleClaims)
               .HasForeignKey(p => p.RoleId)
               .IsRequired();
    }
}

完成这些流畅的 API 配置后,EF Core 在表 AspNetRoleClaims、AspNetUserClaims、AspNetUserRoles 中生成了重复的外键

migrationBuilder.CreateTable(
                name: "AspNetRoleClaims",
                columns: table => new
                {
                    Id = table.Column<int>(nullable: false)
                        .Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn),
                    RoleId = table.Column<string>(type: "CHAR(36)", nullable: false),
                    ClaimType = table.Column<string>(nullable: true),
                    ClaimValue = table.Column<string>(nullable: true),
                    RoleId1 = table.Column<string>(nullable: true) // DUPLICATE
                },
                constraints: table =>
                {
                    table.PrimaryKey("PK_AspNetRoleClaims", x => x.Id);
                    table.ForeignKey(
                        name: "FK_AspNetRoleClaims_AspNetRoles_RoleId",
                        column: x => x.RoleId,
                        principalTable: "AspNetRoles",
                        principalColumn: "Id",
                        onDelete: ReferentialAction.Cascade);
                    table.ForeignKey(
                        name: "FK_AspNetRoleClaims_AspNetRoles_RoleId1",
                        column: x => x.RoleId1,
                        principalTable: "AspNetRoles",
                        principalColumn: "Id",
                        onDelete: ReferentialAction.Restrict);
                });


migrationBuilder.CreateTable(
                name: "AspNetUserClaims",
                columns: table => new
                {
                    Id = table.Column<int>(nullable: false)
                        .Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn),
                    UserId = table.Column<string>(type: "CHAR(36)", nullable: false),
                    ClaimType = table.Column<string>(nullable: true),
                    ClaimValue = table.Column<string>(nullable: true),
                    UserId1 = table.Column<string>(nullable: true) //DUPLICATE
                },
                constraints: table =>
                {
                    table.PrimaryKey("PK_AspNetUserClaims", x => x.Id);
                    table.ForeignKey(
                        name: "FK_AspNetUserClaims_AspNetUsers_UserId",
                        column: x => x.UserId,
                        principalTable: "AspNetUsers",
                        principalColumn: "Id",
                        onDelete: ReferentialAction.Cascade);
                    table.ForeignKey(
                        name: "FK_AspNetUserClaims_AspNetUsers_UserId1",
                        column: x => x.UserId1,
                        principalTable: "AspNetUsers",
                        principalColumn: "Id",
                        onDelete: ReferentialAction.Restrict);
                });


migrationBuilder.CreateTable(
                name: "AspNetUserRoles",
                columns: table => new
                {
                    UserId = table.Column<string>(nullable: false),
                    RoleId = table.Column<string>(nullable: false),
                    Id = table.Column<int>(nullable: false)
                        .Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn),
                    RoleId1 = table.Column<string>(nullable: true), //DUPLICATE
                    UserId1 = table.Column<string>(nullable: true)  //DUPLICATE
                },
                constraints: table =>
                {
                    table.PrimaryKey("PK_AspNetUserRoles", x => new { x.UserId, x.RoleId });
                    table.UniqueConstraint("AK_AspNetUserRoles_Id", x => x.Id);
                    table.ForeignKey(
                        name: "FK_AspNetUserRoles_AspNetRoles_RoleId",
                        column: x => x.RoleId,
                        principalTable: "AspNetRoles",
                        principalColumn: "Id",
                        onDelete: ReferentialAction.Cascade);
                    table.ForeignKey(
                        name: "FK_AspNetUserRoles_AspNetRoles_RoleId1",
                        column: x => x.RoleId1,
                        principalTable: "AspNetRoles",
                        principalColumn: "Id",
                        onDelete: ReferentialAction.Restrict);
                    table.ForeignKey(
                        name: "FK_AspNetUserRoles_AspNetUsers_UserId",
                        column: x => x.UserId,
                        principalTable: "AspNetUsers",
                        principalColumn: "Id",
                        onDelete: ReferentialAction.Cascade);
                    table.ForeignKey(
                        name: "FK_AspNetUserRoles_AspNetUsers_UserId1",
                        column: x => x.UserId1,
                        principalTable: "AspNetUsers",
                        principalColumn: "Id",
                        onDelete: ReferentialAction.Restrict);
                });

如果我做错了什么,请帮我检查一下。先谢谢你们了。


更新1

由于导航属性导致外键重复。如果我在自定义身份模型中删除这些属性,EFCore 将不会生成“DuplicateProperty1”。那我现在该怎么办?


最终更新

我输入的根本原因base.OnModelCreating(builder);在方法结束时 protected override void OnModelCreating(ModelBuilder builder) 。因此,我配置的内部派生类实现的所有 IEntityTypeConfiguration 都将被基类覆盖。最终版本如下:

public class AppDbContext : IdentityDbContext<ApplicationUser, ApplicationRole, string, ApplicationUserClaim, ApplicationUserRole, IdentityUserLogin<string>, ApplicationRoleClaim, IdentityUserToken<string>>
{
    public AppDbContext(DbContextOptions<AppDbContext> options)
            : base(options)
    { }

    protected override void OnModelCreating(ModelBuilder builder)
    {
        base.OnModelCreating(builder);

        var entitiesBuilder = Assembly.GetExecutingAssembly()
                                      .GetTypes()
                                      .Where(type => type.ContainsGenericParameters == false && type.GetInterface(nameof(IEntityBuilder)) != null)
                                      .ToList();

        foreach (var entityBuilder in entitiesBuilder)
        {
            var instance = Activator.CreateInstance(entityBuilder) as IEntityBuilder;
            instance.RunConfiguration(builder);
        }

        // base.OnModelCreating(builder); move this line to the beginning of this method
    }
}

最佳答案

表之间的关系已在 Identity's IdentityDbContext default configuration 中设置。这意味着您不必显式指定它们(使用导航 Prop /流畅的 api 配置)。

但是,您似乎希望使用 Guid/Uuid 而不是字符串作为主键/外键的列类型。如果是这种情况,实现这一目标的正确方法是使用已继承的类的通用变体。

例如,您可以仅定义需要扩展的类并向其添加其他属性 - 这些属性将包含在数据库迁移中:

public class ApplicationUser : IdentityUser<Guid>
{
}

public class ApplicationRole : IdentityRole<Guid>
{
}

除此之外,您的 DbContext 必须继承 IdentityDbContext 的正确变体:

public class ApplicationUserDbContext : IdentityDbContext<ApplicationUser, ApplicationRole, Guid>
{
}

之后,您的迁移将生成一个脚本,将 PK/FK 更改为正确的列类型。如果您需要从Identity扩展其他类型,您可以使用IdentityDbContext的最通用变体,它需要一堆type parameters .

关于c# - AspNet Identity - 重复外键的自定义 IdentityRoleClaim 和 IdentityUserRole 和 IdentityUserClaim,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/56272120/

相关文章:

c# - NuGet包如何知道它们的 `using`指令?

c# - 如何在 Outlook 中区分我的约会和委托(delegate)人约会?

c# - 在 Dialog 类中访问 DbContext 的最佳方法是什么

c# - 比较 MySql 中的 EntityFrameworkCore 字符串列无法生成正确的 SQL

c# - 有效地将多个元素添加到C#中List的开头

c# - NLog 跟踪目标仅适用于跟踪和调试级别,不适用于信息、警告、错误或致命级别

c# - 在 ConfigureServices 中注入(inject)依赖

c# - 数据类型 DateTime 不显示时间

linux - 将带有类库的 .Net Core MVC 应用程序部署到 Ubuntu 16.04 :

c# - EF.Core查询相关数据