AspNet Identity - 复制外键的自定义 IdentityRoleClaim & IdentityUserRole & IdentityUserClaim
AspNet Identity - Custom IdentityRoleClaim & IdentityUserRole & IdentityUserClaim that duplicated foreign key
目前,我正在使用 AspNetCore 和配置 dbcontext,如下所示:
public class AppDbContext : IdentityDbContext<ApplicationUser, ApplicationRole, string, ApplicationUserClaim, ApplicationUserRole, IdentityUserLogin<string>, ApplicationRoleClaim, IdentityUserToken<string>>
{
public AppDbContext(DbContextOptions<AppDbContext> options)
: base(options)
{ }
所以我想自定义 AspNetIdentity,但为了简单起见,我刚刚在 tables:
之间配置了外键
- 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; }
}
下面是派生的 class 为特定的自定义身份模型实施 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 在 table 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)
末尾的根本原因。因此,任何我配置的内部派生 classes 实现的 IEntityTypeConfiguration 将被基础 class 覆盖。最终版本如下:
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
}
}
发生这种情况是因为当您从身份模型继承时,它已经指定了此关系和其他属性。您的代码只是在实体之间添加了额外的关系。您可以在文档中阅读有关身份自定义的更多信息:https://docs.microsoft.com/en-us/aspnet/core/security/authentication/customize-identity-model?view=aspnetcore-2.1
表之间的关系已经在 Identity 的 IdentityDbContext
default configuration 中设置。这意味着您不必明确指定它们(使用导航道具/流畅的 api 配置)。
但是,您似乎希望使用 Guid/Uuid 而不是字符串作为 Primary/Foreign 键的列类型。如果是这种情况,实现该目标的正确方法是使用您已经继承的 类 的通用变体。
例如,您可以只定义 类 您需要扩展并向它们添加其他属性 - 这将包含在数据库迁移中:
public class ApplicationUser : IdentityUser<Guid>
{
}
public class ApplicationRole : IdentityRole<Guid>
{
}
除此之外,您的 DbContext
必须继承 IdentityDbContext
的正确变体:
public class ApplicationUserDbContext : IdentityDbContext<ApplicationUser, ApplicationRole, Guid>
{
}
之后,您的迁移将生成一个脚本来将 PK/FKs 更改为正确的列类型。如果您需要从 Identity 扩展其他类型,您可以使用 IdentityDbContext
的最通用变体,它需要一堆 type parameters.
确保在 AppDbContext
中像下面那样配置 IEntityTypeConfiguration
。
public class ApplicationDbContext : IdentityDbContext<ApplicationUser, ApplicationRole, string, ApplicationUserClaim, ApplicationUserRole, IdentityUserLogin<string>, ApplicationRoleClaim, IdentityUserToken<string>>
{
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
: base(options)
{ }
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
builder.ApplyConfiguration(new ApplicationUserEntityBuilder());
builder.ApplyConfiguration(new ApplicationRoleEntityBuilder());
builder.ApplyConfiguration(new ApplicationUserClaimEntityBuilder());
builder.ApplyConfiguration(new ApplicationUserRoleEntityBuilder());
builder.ApplyConfiguration(new ApplicationRoleClaimEntityBuilder());
}
}
目前,我正在使用 AspNetCore 和配置 dbcontext,如下所示:
public class AppDbContext : IdentityDbContext<ApplicationUser, ApplicationRole, string, ApplicationUserClaim, ApplicationUserRole, IdentityUserLogin<string>, ApplicationRoleClaim, IdentityUserToken<string>>
{
public AppDbContext(DbContextOptions<AppDbContext> options)
: base(options)
{ }
所以我想自定义 AspNetIdentity,但为了简单起见,我刚刚在 tables:
之间配置了外键- 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; }
}
下面是派生的 class 为特定的自定义身份模型实施 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 在 table 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)
末尾的根本原因。因此,任何我配置的内部派生 classes 实现的 IEntityTypeConfiguration 将被基础 class 覆盖。最终版本如下:
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
}
}
发生这种情况是因为当您从身份模型继承时,它已经指定了此关系和其他属性。您的代码只是在实体之间添加了额外的关系。您可以在文档中阅读有关身份自定义的更多信息:https://docs.microsoft.com/en-us/aspnet/core/security/authentication/customize-identity-model?view=aspnetcore-2.1
表之间的关系已经在 Identity 的 IdentityDbContext
default configuration 中设置。这意味着您不必明确指定它们(使用导航道具/流畅的 api 配置)。
但是,您似乎希望使用 Guid/Uuid 而不是字符串作为 Primary/Foreign 键的列类型。如果是这种情况,实现该目标的正确方法是使用您已经继承的 类 的通用变体。
例如,您可以只定义 类 您需要扩展并向它们添加其他属性 - 这将包含在数据库迁移中:
public class ApplicationUser : IdentityUser<Guid>
{
}
public class ApplicationRole : IdentityRole<Guid>
{
}
除此之外,您的 DbContext
必须继承 IdentityDbContext
的正确变体:
public class ApplicationUserDbContext : IdentityDbContext<ApplicationUser, ApplicationRole, Guid>
{
}
之后,您的迁移将生成一个脚本来将 PK/FKs 更改为正确的列类型。如果您需要从 Identity 扩展其他类型,您可以使用 IdentityDbContext
的最通用变体,它需要一堆 type parameters.
确保在 AppDbContext
中像下面那样配置 IEntityTypeConfiguration
。
public class ApplicationDbContext : IdentityDbContext<ApplicationUser, ApplicationRole, string, ApplicationUserClaim, ApplicationUserRole, IdentityUserLogin<string>, ApplicationRoleClaim, IdentityUserToken<string>>
{
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
: base(options)
{ }
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
builder.ApplyConfiguration(new ApplicationUserEntityBuilder());
builder.ApplyConfiguration(new ApplicationRoleEntityBuilder());
builder.ApplyConfiguration(new ApplicationUserClaimEntityBuilder());
builder.ApplyConfiguration(new ApplicationUserRoleEntityBuilder());
builder.ApplyConfiguration(new ApplicationRoleClaimEntityBuilder());
}
}