【问题标题】:Entity Framework Core migration issue实体框架核心迁移问题
【发布时间】: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


【解决方案1】:

您应该尝试像这样配置您的实体:

public class UserConfiguration : IEntityTypeConfiguration<User>
{
    public void Configure(EntityTypeBuilder<User> builder)
    {
        builder.HasMany(x => x.UserRoles)
            .WithOne(x => x.User)
            .HasForeignKey(x => x.UserId);
    }
}

public class RoleConfiguration : IEntityTypeConfiguration<Role>
{
    public void Configure(EntityTypeBuilder<Role> builder)
    {
        builder.HasMany(x => x.UserRoles)
            .WithOne(x => x.Role)
            .HasForeignKey(x => x.RoleId);
    }
}

public void Configure(EntityTypeBuilder<UserRole> builder)
{
    builder.Property(x => x.RoleId).HasColumnName("role_id");
    builder.HasKey(x => new {x.UserId, x.RoleId});
}

当您指定外键时,您应该使用模型属性而不是指定名称,如“role_id”或“user_id”。由于您将该属性定义为例如表中的“role_id”,EF 将在内部处理所有其他事情,并将正确映射您的外键。但是在您提供的示例中,EF 无法理解它应该映射到哪个属性,这就是它生成额外列的原因。

您还应该考虑使用它来重命名数据库级别的所有属性:Naming Conventions for Entity Framework Core Tables and Columns

UserRole 类中的 Id 字段也是多余的,因为您只需要一个复合键 {UserId, RoleId}

【讨论】:

  • 谢谢!我从几个月前开始尝试这种方式,但我永远无法让它工作,所以我最终放弃了对魔术字符串的 HasForeignKey 调用中的 lambdas ......讨厌它,但当时不得不继续前进,它像我一样工作没有 ID 字段。不确定当我开始时那个 SnakeCase 是否很重要,但我一定会检查一下....会让事情变得更容易。至于 Id 复合,即使我知道可以使用复合,我还是选择了这种方式。当我添加一个链接到这个的表时,我必须将两个 id 都带到新表中。我只是更喜欢这种方法。
猜你喜欢
  • 2020-06-21
  • 2021-06-27
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-04-06
  • 2019-11-08
  • 2022-01-28
  • 2019-02-25
相关资源
最近更新 更多