Migraciones de Entity Framework cambiando el nombre de tablas y columnas

118

Cambié el nombre de un par de entidades y sus propiedades de navegación y generé una nueva migración en EF 5. Como es habitual con los cambios de nombre en las migraciones de EF, por defecto iba a soltar objetos y recrearlos. Eso no es lo que quería, así que tuve que crear el archivo de migración desde cero.

    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");
    }

Todo lo que intento hacer es cambiar el nombre dbo.ReportSectionsa dbo.ReportPagesy luego dbo.ReportSectionGroupsa dbo.ReportSections. Entonces necesito cambiar el nombre de la columna de clave externa dbo.ReportPagesde Group_Ida Section_Id.

Estoy eliminando las claves externas e índices que unen las tablas, luego cambio el nombre de las tablas y la columna de clave externa, luego agrego los índices y las claves externas nuevamente. Supuse que esto iba a funcionar pero recibo un error de SQL.

Msg 15248, nivel 11, estado 1, procedimiento sp_rename, línea 215 O el parámetro @objname es ambiguo o el @objtype (COLUMN) reclamado es incorrecto. Msg 4902, nivel 16, estado 1, línea 10 No se puede encontrar el objeto "dbo.ReportSections" porque no existe o no tiene permisos.

No me está resultando fácil averiguar qué está mal aquí. Cualquier idea sería tremendamente útil.

Chev
fuente
¿Cuál de las líneas anteriores falla? ¿Puede rastrear la migración en SQL Server Profiler y verificar el SQL correspondiente?
Albin Sunnanbo

Respuestas:

143

No importa. Estaba haciendo esto más complicado de lo que realmente necesitaba ser.

Esto era todo lo que necesitaba. Los métodos de cambio de nombre simplemente generan una llamada al procedimiento almacenado del sistema sp_rename y supongo que se encargó de todo, incluidas las claves externas con el nuevo nombre de columna.

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");
}
Chev
fuente
29
Tenga cuidado con los nombres de las tablas que tienen puntos. RenameColumngenera una sp_renamedeclaración T-SQL que utiliza usos parsenameinternamente, lo que tiene algunas limitaciones. Entonces, si tiene un nombre de tabla que tiene puntos, por ejemplo, "SubSystemA.Tablename", use:RenameColumn("dbo.[SubSystemA.Tablename]", "OldColumnName", "NewColumnName");
Ilan
10
Esto parece actualizar las columnas a las que se hace referencia en las claves externas, pero no cambia el nombre del FK en sí. Lo cual es una pena, pero probablemente no sea el fin del mundo a menos que sea absolutamente necesario referirse a un FK más adelante por su nombre.
mikesigs
9
@mikesigs que puede usar RenameIndex(..)en su migración para cambiarle el nombre
JoeBrockhaus
1
Recibo una excepción al cambiar el nombre de la columna. probablemente porque la tabla de cambio de nombre todavía no se aplica. Tuve que dividirlo en dos migraciones
Josue Martinez
Con EF6, use RenameTable(..)para cambiar el nombre de los FK y PK. No suena bien, pero es lo que funcionó para mí. Es el método que crea el T-SQL ( execute sp_rename ...) correcto . Si actualiza-database -verbose, lo verá usted mismo.
Giovanni
44

Si no le gusta escribir / cambiar el código requerido en la clase Migración manualmente, puede seguir un enfoque de dos pasos que crea automáticamente el RenameColumncódigo requerido:

Paso uno Utilice ColumnAttributepara introducir el nombre de la nueva columna y luego agregar la migración (p Add-Migration ColumnChanged. Ej. )

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

Paso dos: cambie el nombre de la propiedad y vuelva a aplicar a la misma migración (p Add-Migration ColumnChanged -force. Ej. ) En la Consola del Administrador de paquetes

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

Si observa la clase Migración, puede ver que el código generado automáticamente es RenameColumn.

Hossein Narimani Rad
fuente
¿Cómo puede agregar la misma migración dos veces? Cuando intento esto, obtengo:The name 'Rename_SalesArea' is used by an existing migration.
Andrew S
eche un vistazo al -forceparámetro cuando use add-migration
Hossein Narimani Rad
2
También preste atención, esta publicación no es para EF core
Hossein Narimani Rad
6
Creo que solo necesitas una migración, pero aún dos pasos. 1. Agregue un atributo y cree una "migración de cambio de nombre". 2. Simplemente cambie el nombre de la propiedad. Eso es. De cualquier manera, esto me ahorró un montón de tiempo. ¡Gracias!
Crispy Ninja
1
Seguí los pasos mencionados aquí y fue exitoso. No perdí ningún dato existente. que lo que realmente quería, hacer los cambios sin perder los datos. Pero ejecuto la migración diferente después de cambiar el nombre de la propiedad de la clase, por el lado seguro.
Manojb86
19

Para expandir un poco la respuesta de Hossein Narimani Rad, puede cambiar el nombre de una tabla y columnas usando System.ComponentModel.DataAnnotations.Schema.TableAttribute y System.ComponentModel.DataAnnotations.Schema.ColumnAttribute respectivamente.

Esto tiene un par de beneficios:

  1. Esto no solo creará las migraciones de nombres automáticamente, sino que
  2. también eliminará deliciosamente las claves externas y las volverá a crear con los nuevos nombres de tabla y columna, dando a las claves externas y a las constantes nombres propios.
  3. Todo esto sin perder ningún dato de la tabla

Por ejemplo, agregando [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; }
}

Generará la migración:

    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);
    }
Etienne Morin
fuente
1
Parece que debería ser el valor predeterminado para agregar el atributo de tabla, hace las cosas mucho más simples.
patrick
17

En EF Core, uso las siguientes declaraciones para cambiar el nombre de tablas y columnas:

En cuanto a cambiar el nombre de las tablas:

    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");
    }

En cuanto a cambiar el nombre de las columnas:

    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");
    }
mirind4
fuente
3

En ef core, puede cambiar la migración que se creó después de agregar la migración. Y luego actualice la base de datos. A continuación se muestra una muestra:

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");
}
Abdus Salam Azad
fuente
2

Intenté lo mismo en EF6 (cambiar el nombre de la primera entidad del código). Simplemente cambié el nombre de la clase y agregué una migración usando la consola del administrador de paquetes y listo, se generó automáticamente una migración usando RenameTable (...) para mí. Debo admitir que me aseguré de que el único cambio en la entidad fuera cambiarle el nombre para que no haya columnas nuevas o columnas renombradas, por lo que no puedo estar seguro de si esto es una cosa EF6 o simplemente que EF fue (siempre) capaz de detectar migraciones tan simples.

naskew
fuente
2
Puedo confirmar esto con 6.1.3. Cambia el nombre de la tabla correctamente (no olvide cambiar el nombre DbSeten su DatabaseContexttambién). Cambiar la clave principal causa problemas. La migración intentará eliminarlo y crear uno nuevo. Por lo tanto, debe ajustar eso y hacer lo que la respuesta de Chev es, cambiar el nombre de la columna.
CularBytes
1

Los nombres de tabla y de columna se pueden especificar como parte del mapeo de DbContext. Entonces no es necesario hacerlo en migraciones.

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");
            });
     }
}
Martín Staufcik
fuente