【问题标题】:Entity Framework Migrations renaming tables and columns实体框架迁移重命名表和列
【发布时间】:2012-10-29 02:51:27
【问题描述】:

我重命名了几个实体及其导航属性,并在 EF 5 中生成了一个新的迁移。与 EF 迁移中的重命名一样,默认情况下它会删除对象并重新创建它们。这不是我想要的,所以我几乎不得不从头开始构建迁移文件。

    public override void Up()
    {
        DropForeignKey("dbo.ReportSectionGroups", "Report_Id", "dbo.Reports");
        DropForeignKey("dbo.ReportSections", "Group_Id", "dbo.ReportSectionGroups");
        DropForeignKey("dbo.Editables", "Section_Id", "dbo.ReportSections");
        DropIndex("dbo.ReportSectionGroups", new[] { "Report_Id" });
        DropIndex("dbo.ReportSections", new[] { "Group_Id" });
        DropIndex("dbo.Editables", new[] { "Section_Id" });

        RenameTable("dbo.ReportSections", "dbo.ReportPages");
        RenameTable("dbo.ReportSectionGroups", "dbo.ReportSections");
        RenameColumn("dbo.ReportPages", "Group_Id", "Section_Id");

        AddForeignKey("dbo.ReportSections", "Report_Id", "dbo.Reports", "Id");
        AddForeignKey("dbo.ReportPages", "Section_Id", "dbo.ReportSections", "Id");
        AddForeignKey("dbo.Editables", "Page_Id", "dbo.ReportPages", "Id");
        CreateIndex("dbo.ReportSections", "Report_Id");
        CreateIndex("dbo.ReportPages", "Section_Id");
        CreateIndex("dbo.Editables", "Page_Id");
    }

    public override void Down()
    {
        DropIndex("dbo.Editables", "Page_Id");
        DropIndex("dbo.ReportPages", "Section_Id");
        DropIndex("dbo.ReportSections", "Report_Id");
        DropForeignKey("dbo.Editables", "Page_Id", "dbo.ReportPages");
        DropForeignKey("dbo.ReportPages", "Section_Id", "dbo.ReportSections");
        DropForeignKey("dbo.ReportSections", "Report_Id", "dbo.Reports");

        RenameColumn("dbo.ReportPages", "Section_Id", "Group_Id");
        RenameTable("dbo.ReportSections", "dbo.ReportSectionGroups");
        RenameTable("dbo.ReportPages", "dbo.ReportSections");

        CreateIndex("dbo.Editables", "Section_Id");
        CreateIndex("dbo.ReportSections", "Group_Id");
        CreateIndex("dbo.ReportSectionGroups", "Report_Id");
        AddForeignKey("dbo.Editables", "Section_Id", "dbo.ReportSections", "Id");
        AddForeignKey("dbo.ReportSections", "Group_Id", "dbo.ReportSectionGroups", "Id");
        AddForeignKey("dbo.ReportSectionGroups", "Report_Id", "dbo.Reports", "Id");
    }

我要做的就是将dbo.ReportSections 重命名为dbo.ReportPages,然后将dbo.ReportSectionGroups 重命名为dbo.ReportSections。然后我需要将dbo.ReportPages 上的外键列从Group_Id 重命名为Section_Id

我将删除将表链接在一起的外键和索引,然后重命名表和外键列,然后再次添加索引和外键。我以为这会起作用,但我收到了 SQL 错误。

消息 15248,级别 11,状态 1,过程 sp_rename,第 215 行 参数@objname 不明确或声明的@objtype (COLUMN) 错误。 消息 4902,第 16 层,状态 1,第 10 行 找不到对象“dbo.ReportSections”,因为它不存在或您没有权限。

我很难弄清楚这里出了什么问题。任何见解都会非常有帮助。

【问题讨论】:

  • 以上哪一行失败了?能否在 SQL Server Profiler 中跟踪迁移并检查对应的 SQL?

标签: c# sql-server entity-framework entity-framework-5 entity-framework-migrations


【解决方案1】:

没关系。我让这种方式变得比实际需要的更复杂。

这就是我所需要的。重命名方法只是生成对sp_rename 系统存储过程的调用,我想这会处理所有事情,包括具有新列名的外键。

public override void Up()
{
    RenameTable("ReportSections", "ReportPages");
    RenameTable("ReportSectionGroups", "ReportSections");
    RenameColumn("ReportPages", "Group_Id", "Section_Id");
}

public override void Down()
{
    RenameColumn("ReportPages", "Section_Id", "Group_Id");
    RenameTable("ReportSections", "ReportSectionGroups");
    RenameTable("ReportPages", "ReportSections");
}

【讨论】:

  • 注意表名中包含点的表名。 RenameColumn 生成一个sp_rename T-SQL 语句,它在内部使用parsename,这有一些限制。因此,如果您有一个包含点的表名,例如"SubSystemA.Tablename" 然后使用:RenameColumn("dbo.[SubSystemA.Tablename]", "OldColumnName", "NewColumnName");
  • 这似乎更新了外键中引用的列,但它并没有重命名 FK 本身。这是一种耻辱,但可能不是世界末日,除非你以后绝对需要用它的名字来引用一个 FK。
  • @mikesigs 您可以在迁移中使用RenameIndex(..) 重命名它
  • 重命名列时出现异常。可能是因为重命名表仍未应用。我不得不把它分成两个迁移
  • 对于 EF6,使用 RenameTable(..) 重命名 FK 和 PK。听起来不对,但这对我有用。它是创建正确 T-SQL (execute sp_rename ...) 的方法。如果您执行 update-database -verbose,您会自己看到它。
【解决方案2】:

如果您不喜欢手动编写/更改 Migration 类中所需的代码,您可以按照两步方法自动生成所需的 RenameColumn 代码:

第一步使用ColumnAttribute引入新的列名,然后添加迁移(例如Add-Migration ColumnChanged

public class ReportPages
{
    [Column("Section_Id")]                 //Section_Id
    public int Group_Id{get;set}
}

第二步在包管理器控制台中更改属性名称并再次应用于相同的迁移(例如Add-Migration ColumnChanged -force

public class ReportPages
{
    [Column("Section_Id")]                 //Section_Id
    public int Section_Id{get;set}
}

如果您查看 Migration 类,您会看到自动生成的代码是RenameColumn

【讨论】:

  • 使用 add-migration 时查看-force 参数
  • 还要注意这篇文章不适用于 EF 内核
  • 我认为您只需要一个迁移,但仍然需要两个步骤。 1.添加属性并创建“重命名迁移” 2.只需更改属性名称。而已。无论哪种方式,这只是为我节省了大量时间。谢谢!
  • 我按照这里提到的步骤,它是成功的。我没有丢失任何现有数据。这是我真正想要的,在不丢失数据的情况下进行更改。但是为了安全起见,我在重命名类的属性名称后运行了不同的迁移。
  • 如果您想将此方法用于 EF Core,请务必使用两个单独的迁移执行这两个步骤:ColumnChanged_A,然后是 ColumnChanged_B。如果你不这样做,你就有可能让你的 DBModelSnapshot 引用旧的属性名称。 (也许这总是会在以后的迁移中自行排序,但似乎仍然有点冒险)
【解决方案3】:

在 EF Core 中,我使用以下语句重命名表和列:

至于重命名表:

    protected override void Up(MigrationBuilder migrationBuilder)
    {
        migrationBuilder.RenameTable(
            name: "OldTableName",
            schema: "dbo",
            newName: "NewTableName",
            newSchema: "dbo");
    }

    protected override void Down(MigrationBuilder migrationBuilder)
    {
        migrationBuilder.RenameTable(
            name: "NewTableName",
            schema: "dbo",
            newName: "OldTableName",
            newSchema: "dbo");
    }

至于重命名列:

    protected override void Up(MigrationBuilder migrationBuilder)
    {
        migrationBuilder.RenameColumn(
            name: "OldColumnName",
            table: "TableName",
            newName: "NewColumnName",
            schema: "dbo");
    }

    protected override void Down(MigrationBuilder migrationBuilder)
    {
        migrationBuilder.RenameColumn(
            name: "NewColumnName",
            table: "TableName",
            newName: "OldColumnName",
            schema: "dbo");
    }

【讨论】:

    【解决方案4】:

    要扩展 Hossein Narimani Rad 的答案,您可以分别使用 System.ComponentModel.DataAnnotations.Schema.TableAttribute 和 System.ComponentModel.DataAnnotations.Schema.ColumnAttribute 重命名表和列。

    这有几个好处:

    1. 这不仅会自动创建名称迁移,而且
    2. 它还可以很好地删除任何外键,并根据新的表和列名重新创建它们,为外键和容器提供正确的名称。
    3. 所有这些都不会丢失任何表数据

    例如添加[Table("Staffs")]:

    [Table("Staffs")]
    public class AccountUser
    {
        public long Id { get; set; }
    
        public long AccountId { get; set; }
    
        public string ApplicationUserId { get; set; }
    
        public virtual Account Account { get; set; }
    
        public virtual ApplicationUser User { get; set; }
    }
    

    将生成迁移:

        protected override void Up(MigrationBuilder migrationBuilder)
        {
            migrationBuilder.DropForeignKey(
                name: "FK_AccountUsers_Accounts_AccountId",
                table: "AccountUsers");
    
            migrationBuilder.DropForeignKey(
                name: "FK_AccountUsers_AspNetUsers_ApplicationUserId",
                table: "AccountUsers");
    
            migrationBuilder.DropPrimaryKey(
                name: "PK_AccountUsers",
                table: "AccountUsers");
    
            migrationBuilder.RenameTable(
                name: "AccountUsers",
                newName: "Staffs");
    
            migrationBuilder.RenameIndex(
                name: "IX_AccountUsers_ApplicationUserId",
                table: "Staffs",
                newName: "IX_Staffs_ApplicationUserId");
    
            migrationBuilder.RenameIndex(
                name: "IX_AccountUsers_AccountId",
                table: "Staffs",
                newName: "IX_Staffs_AccountId");
    
            migrationBuilder.AddPrimaryKey(
                name: "PK_Staffs",
                table: "Staffs",
                column: "Id");
    
            migrationBuilder.AddForeignKey(
                name: "FK_Staffs_Accounts_AccountId",
                table: "Staffs",
                column: "AccountId",
                principalTable: "Accounts",
                principalColumn: "Id",
                onDelete: ReferentialAction.Cascade);
    
            migrationBuilder.AddForeignKey(
                name: "FK_Staffs_AspNetUsers_ApplicationUserId",
                table: "Staffs",
                column: "ApplicationUserId",
                principalTable: "AspNetUsers",
                principalColumn: "Id",
                onDelete: ReferentialAction.Restrict);
        }
    
        protected override void Down(MigrationBuilder migrationBuilder)
        {
            migrationBuilder.DropForeignKey(
                name: "FK_Staffs_Accounts_AccountId",
                table: "Staffs");
    
            migrationBuilder.DropForeignKey(
                name: "FK_Staffs_AspNetUsers_ApplicationUserId",
                table: "Staffs");
    
            migrationBuilder.DropPrimaryKey(
                name: "PK_Staffs",
                table: "Staffs");
    
            migrationBuilder.RenameTable(
                name: "Staffs",
                newName: "AccountUsers");
    
            migrationBuilder.RenameIndex(
                name: "IX_Staffs_ApplicationUserId",
                table: "AccountUsers",
                newName: "IX_AccountUsers_ApplicationUserId");
    
            migrationBuilder.RenameIndex(
                name: "IX_Staffs_AccountId",
                table: "AccountUsers",
                newName: "IX_AccountUsers_AccountId");
    
            migrationBuilder.AddPrimaryKey(
                name: "PK_AccountUsers",
                table: "AccountUsers",
                column: "Id");
    
            migrationBuilder.AddForeignKey(
                name: "FK_AccountUsers_Accounts_AccountId",
                table: "AccountUsers",
                column: "AccountId",
                principalTable: "Accounts",
                principalColumn: "Id",
                onDelete: ReferentialAction.Cascade);
    
            migrationBuilder.AddForeignKey(
                name: "FK_AccountUsers_AspNetUsers_ApplicationUserId",
                table: "AccountUsers",
                column: "ApplicationUserId",
                principalTable: "AspNetUsers",
                principalColumn: "Id",
                onDelete: ReferentialAction.Restrict);
        }
    

    【讨论】:

    • 似乎应该默认添加表格属性,让事情变得简单得多。
    • 非常感谢,还想指出,在第一次迁移之后,您通常会删除属性并重命名表。 此时再运行一次迁移非常重要 - 您可以将其命名为“TempMigration”,然后删除实际的迁移,但使用新的类名更新上下文快照很重要。
    • @Groo,当我在重命名实际属性后运行第二次迁移时,我在新的迁移脚本中看到了一个删除表和类似的东西。您是说我们可以放弃该迁移并保留快照,对吗?
    • @Emulic:不,通常你不应该得到任何 drop 语句。因此,当您在第一次迁移后删除属性并重命名列时,下一次迁移的 UpDown 方法应该是空的,只是应该更新快照文件。也许您需要检查所有拼写是否正常?
    • @groo,谢谢。是的,我意识到我犯了一个错误,在步骤 1 中缺少实体重命名的复数 s。在我修复了这个问题之后,在最后创建迁移之后它实际上是空的。感谢您提供的所有重要信息。
    【解决方案5】:

    我刚刚在 EF6 中尝试过相同的方法(代码优先实体重命名)。我只是重命名了该类并使用包管理器控制台添加了一个迁移,瞧,使用 RenameTable(...) 的迁移是自动为我生成的。我必须承认,我确保对实体的唯一更改是重命名它,因此没有新列或重命名列,所以我无法确定这是 EF6 的事情还是只是 EF (总是)能够检测到如此简单的迁移。

    【讨论】:

    • 我可以用 6.1.3 确认它确实正确地重命名了表(不要忘记在你的 DatabaseContext 中重命名 DbSet)。更改主键确实会引起麻烦。迁移将尝试删除它并创建一个新的。因此,您需要对其进行调整,并按照 Chev 的回答重命名该列。
    【解决方案6】:

    表名和列名可以指定为DbContext 映射的一部分。那么在迁移中就不需要这样做了。

    public class MyContext : DbContext
    {
        protected override void OnModelCreating(DbModelBuilder modelBuilder)
        {
            modelBuilder.Entity<Restaurant>()
                .HasMany(p => p.Cuisines)
                .WithMany(r => r.Restaurants)
                .Map(mc =>
                {
                    mc.MapLeftKey("RestaurantId");
                    mc.MapRightKey("CuisineId");
                    mc.ToTable("RestaurantCuisines");
                });
         }
    }
    

    【讨论】:

      【解决方案7】:

      在 ef core 中,您可以更改添加迁移后创建的迁移。然后做更新数据库。示例如下:

      protected override void Up(MigrationBuilder migrationBuilder)
      {
          migrationBuilder.RenameColumn(name: "Type", table: "Users", newName: "Discriminator", schema: "dbo");
      }
      
      protected override void Down(MigrationBuilder migrationBuilder)
      {            
          migrationBuilder.RenameColumn(name: "Discriminator", table: "Users", newName: "Type", schema: "dbo");
      }
      

      【讨论】:

        【解决方案8】:

        对于 EF Core migrationBuilder.RenameColumn 通常可以正常工作,但有时您还必须处理索引。

        migrationBuilder.RenameColumn(name: "Identifier", table: "Questions", newName: "ChangedIdentifier", schema: "dbo");
        

        更新数据库时的错误信息示例:

        Microsoft.Data.SqlClient.SqlException (0x80131904):索引 “IX_Questions_Identifier”依赖于“标识符”列。

        索引“IX_Questions_Identifier”依赖于“Identifier”列。

        重命名列标识符失败,因为一个或多个对象访问 本栏目。

        在这种情况下,您必须像这样进行重命名:

        migrationBuilder.DropIndex(
            name: "IX_Questions_Identifier",
            table: "Questions");
        
        migrationBuilder.RenameColumn(name: "Identifier", table: "Questions", newName: "ChangedIdentifier", schema: "dbo");
        
        migrationBuilder.CreateIndex(
            name: "IX_Questions_ChangedIdentifier",
            table: "Questions",
            column: "ChangedIdentifier",
            unique: true,
            filter: "[ChangedIdentifier] IS NOT NULL");
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2012-07-25
          • 1970-01-01
          • 2015-05-11
          相关资源
          最近更新 更多