Reputation: 821
currently, I am using AspNetCore and the configuration dbcontext as below:
public class AppDbContext : IdentityDbContext<ApplicationUser, ApplicationRole, string, ApplicationUserClaim, ApplicationUserRole, IdentityUserLogin<string>, ApplicationRoleClaim, IdentityUserToken<string>>
{
public AppDbContext(DbContextOptions<AppDbContext> options)
: base(options)
{ }
So I would like to customize AspNetIdentity, but for simplicity I have just configured foreign key between tables:
<string>
<string>
<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; }
}
And follows as are derived class implement IEntityTypeConfiguration for particular custom identity model.
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();
}
}
After these all fluent API configuration, EF Core generated duplicate Foreign Key in 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);
});
Please review it for me if anythings I did wrong. Thank you guys in advance.
UPDATE 1
Because of Navigation Property that causes duplicate foreign key. If I remove these properties in custom identity model that EFCore will not generate "DuplicateProperty1". So what I have to do now?
FINAL UPDATE
Root cause that I put base.OnModelCreating(builder);
at the end of method protected override void OnModelCreating(ModelBuilder builder)
. Therefore, anythings I have configured inner derived classes implemented IEntityTypeConfiguration that will be override by base class. And final version as below:
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
}
}
Upvotes: 3
Views: 8268
Reputation: 30046
Make sure you configure the IEntityTypeConfiguration
like below in AppDbContext
.
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());
}
}
Upvotes: 0
Reputation: 3900
Relationships between tables are already set inside Identity's IdentityDbContext
default configuration. That means you don't have to specify them explicitly (using navigational props / fluent api configuration).
However, it looks like you want to have Guid/Uuid instead of string as a column type for your Primary/Foreign keys. If that's the case, the proper way to achieve that is to use generic variants of classes you've already inherited.
For example, you can define only classes you need to expand and add additional properties to them - which will be included into DB Migration:
public class ApplicationUser : IdentityUser<Guid>
{
}
public class ApplicationRole : IdentityRole<Guid>
{
}
In addition to that, your DbContext
must inherit proper variant of IdentityDbContext
:
public class ApplicationUserDbContext : IdentityDbContext<ApplicationUser, ApplicationRole, Guid>
{
}
After that, your migration will generate a script to change PK/FKs into proper column type. If you need to expand other types from Identity, you can use most generic variant of IdentityDbContext
which takes a bunch of type parameters.
Upvotes: 3
Reputation: 588
This is happening because when you inherit from identity models it already has this relations and other properties specified. Your code just added additional relations between entities. You can read more about identity customization in documentation: https://learn.microsoft.com/en-us/aspnet/core/security/authentication/customize-identity-model?view=aspnetcore-2.1
Upvotes: 0