Migrações do Entity Framework renomeando tabelas e colunas

118

Eu renomei duas entidades e suas propriedades de navegação e gerei uma nova Migração no EF 5. Como é usual com renomeações em migrações EF, por padrão ele iria descartar objetos e recriá-los. Não era isso que eu queria, então tive que criar o arquivo de migração do zero.

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

Tudo o que estou tentando fazer é renomear dbo.ReportSectionspara dbo.ReportPagese depois dbo.ReportSectionGroupspara dbo.ReportSections. Em seguida, preciso renomear a coluna de chave estrangeira dbo.ReportPagesde Group_Idpara Section_Id.

Estou eliminando as chaves estrangeiras e os índices que vinculam as tabelas, depois renomeio as tabelas e a coluna da chave estrangeira e, em seguida, estou adicionando os índices e as chaves estrangeiras novamente. Achei que isso iria funcionar, mas estou recebendo um erro de SQL.

Msg 15248, nível 11, estado 1, procedimento sp_rename, linha 215 O parâmetro @objname é ambíguo ou o @objtype reivindicado (COLUMN) está incorreto. Msg 4902, Nível 16, Estado 1, Linha 10 Não é possível localizar o objeto "dbo.ReportSections" porque ele não existe ou você não tem permissões.

Não estou tendo um tempo fácil para descobrir o que está errado aqui. Qualquer insight seria extremamente útil.

Chev
fonte
Qual das linhas acima falha? Você pode rastrear a migração no SQL Server Profiler e verificar o SQL correspondente?
Albin Sunnanbo

Respostas:

143

Deixa pra lá. Eu estava tornando isso mais complicado do que realmente precisava ser.

Isso era tudo de que eu precisava. Os métodos rename apenas geram uma chamada para o procedimento armazenado do sistema sp_rename e acho que isso cuidou de tudo, incluindo as chaves estrangeiras com o novo nome da coluna.

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
fonte
29
Tenha cuidado com os nomes das tabelas que contêm pontos. RenameColumngera uma sp_renameinstrução T-SQL que usa usa parsenameinternamente que tem algumas limitações. Portanto, se você tiver um nome de tabela com pontos, por exemplo, "SubSystemA.Tablename", use:RenameColumn("dbo.[SubSystemA.Tablename]", "OldColumnName", "NewColumnName");
Ilan
10
Isso parece atualizar as colunas referenciadas nas Chaves Estrangeiras, mas não renomeia o próprio FK. O que é uma pena, mas provavelmente não é o fim do mundo, a menos que você precise se referir a um FK mais tarde pelo nome.
mikesigs
9
@mikesigs você pode usar RenameIndex(..)em sua migração para renomeá-lo
JoeBrockhaus
1
Estou recebendo uma exceção ao renomear a coluna. provavelmente porque a tabela de renomeação ainda não foi aplicada. Tive que dividi-lo em duas migrações
Josue Martinez
Com EF6, use RenameTable(..)para renomear os FKs e PKs . Não parece certo, mas é o que funcionou para mim. É o método que cria o T-SQL ( execute sp_rename ...) correto . Se você atualizar o banco de dados -verbose, verá por si mesmo.
Giovanni
44

Se você não gosta de escrever / alterar o código necessário na classe Migration manualmente, você pode seguir uma abordagem de duas etapas que cria automaticamente o RenameColumncódigo que é necessário:

Primeiro passo Use o ColumnAttributepara introduzir o novo nome da coluna e, em seguida, adicionar migração (por exemplo Add-Migration ColumnChanged)

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

Etapa dois altere o nome da propriedade e aplique novamente à mesma migração (por exemplo Add-Migration ColumnChanged -force) no console do gerenciador de pacotes

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

Se você olhar para a classe Migration, poderá ver o código gerado automaticamente é RenameColumn.

Hossein Narimani Rad
fonte
Como você consegue adicionar a mesma migração duas vezes? Quando tento fazer isso, obtenho:The name 'Rename_SalesArea' is used by an existing migration.
Andrew S
dê uma olhada no -forceparâmetro ao usar a migração de adição
Hossein Narimani Rad
2
também preste atenção, este post não é para o núcleo da EF
Hossein Narimani Rad
6
Acho que você só precisa de uma migração, mas ainda duas etapas. 1. Adicione o atributo e crie "renomear migração" 2. Basta alterar o nome da propriedade. É isso aí. De qualquer forma, isso me economizou muito tempo. Obrigado!
Crispy Ninja
1
Eu segui os passos mencionados aqui e tive sucesso. Não perdi nenhum dado existente. que o que eu realmente queria, fazer as alterações sem perder os dados. Mas eu executo a migração diferente depois de renomear o nome da propriedade da classe, para o lado seguro.
Manojb86
19

Para expandir um pouco a resposta de Hossein Narimani Rad, você pode renomear uma tabela e colunas usando System.ComponentModel.DataAnnotations.Schema.TableAttribute e System.ComponentModel.DataAnnotations.Schema.ColumnAttribute respectivamente.

Isso tem alguns benefícios:

  1. Isso não só criará as migrações de nome automaticamente, mas
  2. ele também deleta deliciosamente quaisquer chaves estrangeiras e as recria com os novos nomes de tabela e coluna, fornecendo nomes próprios às chaves estrangeiras e às restrições.
  3. Tudo isso sem perder nenhum dado da tabela

Por exemplo, adicionando [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; }
}

Irá gerar a migração:

    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
fonte
1
Parece que deveria ser o padrão adicionar o atributo de tabela, torna as coisas muito mais simples.
patrick
17

No EF Core, eu uso as seguintes instruções para renomear tabelas e colunas:

Quanto à renomeação de tabelas:

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

Quanto à renomeação de colunas:

    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
fonte
3

No ef core, você pode alterar a migração que foi criada após adicionar migração. E então atualize o banco de dados. Um exemplo é dado abaixo:

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
fonte
2

Acabei de tentar o mesmo no EF6 (renomear entidade de código primeiro). Simplesmente renomeei a classe e adicionei uma migração usando o console do gerenciador de pacotes e voila, uma migração usando RenameTable (...) foi gerada automaticamente para mim. Tenho que admitir que me certifiquei de que a única alteração na entidade foi renomeá-la para que não haja novas colunas ou colunas renomeadas, então não posso ter certeza se isso é uma coisa EF6 ou apenas que EF foi (sempre) capaz de detectar essas migrações simples.

naskew
fonte
2
Posso confirmar isso com 6.1.3. Ele renomeia corretamente a tabela (não se esqueça de renomear DbSetno seu DatabaseContexttambém). Alterar a chave primária causa problemas. A migração tentará excluí-lo e criar um novo. Portanto, você precisa ajustar isso e fazer como a resposta de Chev: renomear a coluna.
CularBytes
1

Nomes de tabelas e nomes de colunas podem ser especificados como parte do mapeamento de DbContext. Então não há necessidade de fazer isso nas migrações.

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