Migracje Entity Framework zmieniające nazwy tabel i kolumn

118

Zmieniłem nazwy kilku jednostek i ich właściwości nawigacyjnych i wygenerowałem nową migrację w EF 5. Jak zwykle w przypadku zmian nazw w migracjach EF, domyślnie zamierzałem porzucić obiekty i je odtworzyć. Nie tego chciałem, więc prawie musiałem zbudować plik migracji od zera.

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

Wszystko, co próbuję zrobić, to zmienić nazwę dbo.ReportSectionsna, dbo.ReportPagesa następnie dbo.ReportSectionGroupsna dbo.ReportSections. Następnie muszę zmienić nazwę kolumny klucza obcego dbo.ReportPagesz Group_Idna Section_Id.

Porzucam klucze obce i indeksy łączące tabele, potem zmieniam nazwy tabel i kolumny klucza obcego, a następnie ponownie dodaję indeksy i klucze obce. Zakładałem, że to zadziała, ale pojawia się błąd SQL.

Msg 15248, poziom 11, stan 1, procedura sp_rename, wiersz 215 albo parametr @objname jest niejednoznaczny, albo żądany @objtype (COLUMN) jest nieprawidłowy. Msg 4902, poziom 16, stan 1, wiersz 10 Nie można znaleźć obiektu „dbo.ReportSections”, ponieważ nie istnieje lub nie masz uprawnień.

Nie mam łatwo dowiedzieć się, co jest nie tak. Każdy wgląd byłby niezwykle pomocny.

Chev
źródło
Która z powyższych linii zawodzi? Czy możesz prześledzić migrację w programie SQL Server Profiler i sprawdzić odpowiedni kod SQL?
Albin Sunnanbo

Odpowiedzi:

143

Nieważne. Robiłem to bardziej skomplikowanym, niż było to naprawdę konieczne.

To było wszystko, czego potrzebowałem. Metody zmiany nazwy po prostu generują wywołanie procedury przechowywanej w systemie sp_rename i myślę, że zadbała o wszystko, w tym klucze obce z nową nazwą kolumny.

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
źródło
29
Uważaj na nazwy tabel zawierające kropki. RenameColumngeneruje sp_renameinstrukcję T-SQL używającą parsenamewewnętrznie, która ma pewne ograniczenia. Więc jeśli masz nazwę tabeli, która zawiera kropki, np. „SubSystemA.Tablename”, użyj:RenameColumn("dbo.[SubSystemA.Tablename]", "OldColumnName", "NewColumnName");
Ilan
10
Wydaje się, że aktualizuje to kolumny, do których odwołuje się klucz obcy, ale nie zmienia nazwy samego FK. Co jest wstydem, ale prawdopodobnie nie koniec świata, chyba że absolutnie będziesz musiał później odnosić się do FK po jego nazwie.
mikesigs
9
@mikesigs, którego możesz użyć RenameIndex(..)podczas migracji, aby zmienić jego nazwę
JoeBrockhaus
1
Podczas zmiany nazwy kolumny pojawia się wyjątek. prawdopodobnie dlatego, że tabela zmian nazwy nadal nie jest stosowana. Musiałem podzielić to na dwie migracje
Josue Martinez
W przypadku EF6 użyj, RenameTable(..)aby zmienić nazwy FK i PK. To nie brzmi dobrze, ale to działa dla mnie. Jest to metoda, która tworzy poprawną T-SQL ( execute sp_rename ...). Jeśli wykonasz update-database -verbose, zobaczysz to na własne oczy.
Giovanni
44

Jeśli nie lubisz ręcznie pisać / zmieniać wymaganego kodu w klasie Migration, możesz zastosować dwuetapowe podejście, które automatycznie tworzy RenameColumnwymagany kod:

Krok pierwszy Użyj, ColumnAttributeaby wprowadzić nową nazwę kolumny, a następnie dodaj migrację (np. Add-Migration ColumnChanged)

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

Krok drugi: Zmień nazwę właściwości i ponownie zastosuj do tej samej migracji (np. Add-Migration ColumnChanged -force) W konsoli Menedżera pakietów

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

Jeśli spojrzysz na klasę migracji, zobaczysz, że automatycznie wygenerowany kod to RenameColumn.

Hossein Narimani Rad
źródło
Jak możesz dwukrotnie dodać tę samą migrację? Kiedy próbuję tego, otrzymuję:The name 'Rename_SalesArea' is used by an existing migration.
Andrew S
spójrz na -forceparametr podczas korzystania z migracji dodawania
Hossein Narimani Rad
2
zwróć też uwagę, że ten post nie jest dla rdzenia EF
Hossein Narimani Rad
7
Myślę, że potrzebujesz tylko jednej migracji, ale nadal dwóch kroków. 1. Dodaj atrybut i utwórz „zmianę nazwy migracji”. 2. Zmień tylko nazwę właściwości. Otóż ​​to. Tak czy inaczej, zaoszczędziło mi to mnóstwo czasu. Dzięki!
Crispy Ninja
1
Wykonałem wymienione tutaj kroki i udało się. Nie straciłem żadnych istniejących danych. że to, czego naprawdę chciałem, wprowadzaj zmiany bez utraty danych. Ale uruchamiam inną migrację po zmianie nazwy właściwości klasy, ze względów bezpieczeństwa.
Manojb86
19

Aby nieco rozwinąć odpowiedź Hosseina Narimani Rad, możesz zmienić nazwę zarówno tabeli, jak i kolumn, używając odpowiednio System.ComponentModel.DataAnnotations.Schema.TableAttribute i System.ComponentModel.DataAnnotations.Schema.ColumnAttribute.

Ma to kilka zalet:

  1. Nie tylko spowoduje to automatyczne utworzenie migracji nazw, ale także
  2. będzie również cudownie usuwać wszelkie klucze obce i odtwarzać je z nowymi nazwami tabel i kolumn, nadając klucze obce i stałe nazwy własne.
  3. Wszystko to bez utraty danych w tabeli

Na przykład dodanie [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; }
}

Wygeneruje migrację:

    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
źródło
1
Wydaje się, że powinno być domyślnie dodawanie atrybutu tabeli, znacznie upraszcza to.
patrick
17

W EF Core używam następujących instrukcji do zmiany nazw tabel i kolumn:

Jeśli chodzi o zmianę nazw tabel:

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

Jeśli chodzi o zmianę nazw kolumn:

    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
źródło
3

W ef core można zmienić migrację, która została utworzona po dodaniu migracji. A następnie wykonaj aktualizację bazy danych. Próbka podała poniżej:

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
źródło
2

Właśnie spróbowałem tego samego w EF6 (zmiana nazwy pierwszej jednostki kodu). Po prostu zmieniłem nazwę klasy i dodałem migrację za pomocą konsoli menedżera pakietów i voila, migracja za pomocą RenameTable (...) została automatycznie wygenerowana dla mnie. Muszę przyznać, że upewniłem się, że jedyną zmianą w encji była zmiana jej nazwy, więc nie ma nowych kolumn ani kolumn o zmienionych nazwach, więc nie mogę być pewien, czy jest to sprawa EF6, czy po prostu EF był (zawsze) w stanie wykryć takie proste migracje.

naskew
źródło
2
Mogę to potwierdzić za pomocą 6.1.3. Poprawnie zmienia nazwę tabeli (nie zapomnij również zmienić nazwy DbSetw twoim DatabaseContext). Zmiana klucza podstawowego powoduje problemy. Migracja spróbuje go usunąć i utworzyć nową. Więc musisz to dostosować i zrobić tak, jak odpowiedź Cheva, zmień nazwę kolumny.
CularBytes
1

Nazwy tabel i nazw kolumn można określić jako część odwzorowania DbContext. Wtedy nie ma potrzeby tego robić w przypadku migracji.

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
źródło