【发布时间】:2021-09-01 12:27:36
【问题描述】:
在 EF Core 3.1.16 中存在多对多关系问题。
非常基本的概念:用户/用户角色/角色。
使用 MS Identity 所以依赖于 IdentityUserRole 类。由于该类已经具有 UserId 和 RoleId 属性,因此 EF 正尝试按照约定生成这些字段。我还没有想出一种方法来告诉 EF 将 MS UserId 和 RoleId 字段视为与我想要在 UserRole 实体类上的对象中的 User.Id 和 Role.Id 字段相同的字段。我们使用 IEntityTypeConfiguration 实例来创建 EF 使用的数据库架构,而不是基于约定的方法。生成模式的代码如下。
用户
public class User : IdentityUser<int>, IEntityCustom
{
// Custom field for testing purpose only...wouldn't actually store this.
public int Age { get; set; }
public List<UserRole> UserRoles { get; set; }
}
public class UserTypeConfiguration : BaseTypeConfiguration<User>, IEntityTypeConfiguration<User>
{
public UserTypeConfiguration() : base("permissions", "user") { }
public override void Configure(EntityTypeBuilder<User> builder)
{
base.Configure(builder);
#region "Hiding other properties"
// Other fields are here that I omitted as they are just lowercasing only
// and dont have relation to what is going on in the question. user_name
// field below is same as all the code omitted just with that field name pair.
#endregion
builder.Property(x => x.UserName).HasColumnName("user_name");
builder.HasMany(x => x.UserRoles).WithOne(x => x.User).HasForeignKey("user_id");
builder.HasData(new User
{
Id = 1,
AccessFailedCount = 0,
Age = 12,
ConcurrencyStamp = Guid.NewGuid().ToString(),
Email = "test@yopmail.com",
EmailConfirmed = true,
LockoutEnabled = false,
NormalizedEmail = "test@yopmail.com",
NormalizedUserName = "test",
PasswordHash = "hash",
PhoneNumber = "5551231234",
PhoneNumberConfirmed = true,
SecurityStamp = Guid.NewGuid().ToString(),
TwoFactorEnabled = false,
UserName = "test"
});
}
}
USER_ROLE
public class UserRole : IdentityUserRole<int>, IEntityCustom
{
public int Id { get; set; }
public User User { get; set; }
public Role Role { get; set; }
public UserRole() { }
public UserRole(User user, Role role)
{
User = user;
Role = role;
}
}
public class UserRoleTypeConfiguration : BaseTypeConfiguration<UserRole>, IEntityTypeConfiguration<UserRole>
{
public UserRoleTypeConfiguration() : base("permissions", "user_role") { }
public override void Configure(EntityTypeBuilder<UserRole> builder)
{
base.Configure(builder);
// Gens the right schema, but fails at runtime on
// insert to user_role table with duplicate "role_id" column exists on table "user_role"
builder.Property<int>("UserId").HasColumnName("user_id");
builder.Property<int>("RoleId").HasColumnName("role_id");
// These 2 lines of code seem dumb, but they fix the schema
// Only way I could find to accomplish it tho...
builder.Property<int>("user_id").HasColumnName("user_id");
builder.Property<int>("role_id").HasColumnName("role_id");
builder.HasOne(x => x.User).WithMany(x => x.UserRoles).HasForeignKey("user_id");
builder.HasOne(x => x.Role).WithMany(x => x.UserRoles).HasForeignKey("role_id");
#region Region with all the ways I tried that didnt work.
// Removed all the stuff that I tried that didn't get the model right.
// ie....duplicate fields, etc.
#endregion
}
}
角色
public class Role : IdentityRole<int>, IEntityCustom
{
// Another custom field for testing. eg. Meaning = "SYSTEMADMIN"
public string Meaning { get; set; }
// I really dont want this navigation and my real code doesnt have it currently.
// I couldnt get it working without, so I finally just added to see if I could get it
// working as full many to many relation and would remove later.
public List<UserRole> UserRoles { get; set; }
}
public class RoleTypeConfiguration : BaseTypeConfiguration<Role>, IEntityTypeConfiguration<Role>
{
public RoleTypeConfiguration() : base("permissions", "role") { }
public override void Configure(EntityTypeBuilder<Role> builder)
{
base.Configure(builder);
builder.Property(x => x.Name).HasColumnName("name");
builder.Property(x => x.ConcurrencyStamp).HasColumnName("concurrency_stamp");
builder.Property(x => x.NormalizedName).HasColumnName("normalized_name");
builder.Property(x => x.Meaning).HasColumnName("meaning");
builder.HasMany(x => x.UserRoles).WithOne(x => x.Role).HasForeignKey("role_id");
builder.HasData(new Role
{
Id = 1,
ConcurrencyStamp = Guid.NewGuid().ToString(),
Meaning = "SYSADMIN",
Name = "System Admin",
NormalizedName = "system admin"
});
builder.HasData(new Role
{
Id = 2,
ConcurrencyStamp = Guid.NewGuid().ToString(),
Meaning = "CONTENTADMIN",
Name = "Content Admin",
NormalizedName = "content admin"
});
}
}
基本类型配置
public class BaseTypeConfiguration<TEntity> : IEntityTypeConfiguration<TEntity> where TEntity : class, IEntityCustom
{
private readonly string _schemaName;
private readonly string _tableName;
public BaseTypeConfiguration(string schemaName, string tableName)
{
_schemaName = schemaName;
_tableName = tableName;
}
public virtual void Configure(EntityTypeBuilder<TEntity> builder)
{
Console.WriteLine("schema:" + _schemaName);
Console.WriteLine("table:" + _tableName);
builder.Metadata.SetSchema(_schemaName);
builder.ToTable(_tableName);
builder.HasKey(x => x.Id);
builder.Property(x => x.Id)
.HasColumnName("id")
.ValueGeneratedOnAdd();
}
}
IENTITYCUSTOM
public interface IEntityCustom
{
int Id { get; set; }
}
输出
在下面的输出部分中,是迁移过程创建的内容。此示例在模型上为 user_role 生成正确的字段(即...单个 user_id 和 role_id 字段,加上核心 id 字段),但是当您查看它生成的 FK refs 时,两个字段都是重复的。我尝试运行此方案,它确实会运行迁移并正确创建表,但是当您尝试对 user_role 表运行插入时,EF 会引发以下异常:
“42701:列 role_id 指定了多次。”
migrationBuilder.CreateTable(
name: "user_role",
schema: "permissions",
columns: table => new
{
id = table.Column<int>(nullable: false)
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
user_id = table.Column<int>(nullable: false),
role_id = table.Column<int>(nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_user_role", x => x.id);
table.ForeignKey(
name: "FK_user_role_role_role_id",
column: x => x.role_id,
principalSchema: "permissions",
principalTable: "role",
principalColumn: "id",
onDelete: ReferentialAction.Cascade);
table.ForeignKey(
name: "FK_user_role_user_user_id",
column: x => x.user_id,
principalSchema: "permissions",
principalTable: "user",
principalColumn: "id",
onDelete: ReferentialAction.Cascade);
table.ForeignKey(
name: "FK_user_role_role_role_id1",
column: x => x.role_id,
principalSchema: "permissions",
principalTable: "role",
principalColumn: "id",
onDelete: ReferentialAction.Cascade);
table.ForeignKey(
name: "FK_user_role_user_user_id1",
column: x => x.user_id,
principalSchema: "permissions",
principalTable: "user",
principalColumn: "id",
onDelete: ReferentialAction.Cascade);
});
创建的数据库
【问题讨论】:
-
我很感激您希望提供背景和尽可能多的信息,但这更像是一个小说而不是一个问题。将来,尽量减少您的问题,使其更容易接近。
标签: c# entity-framework-core entity-framework-migrations npgsql