14

Używamy Entity Framework 5.0 Code First i Automatic Migrations.Kod EF5 Pierwsze migracje: "Nazwy kolumn w każdej tabeli muszą być unikalne" błąd po użyciu RenameColumn

miałem klasy tak:

public class TraversalZones 
{ 
    public int Low { get; set; } 
    public int High { get; set; } 
}​ 

Wtedy zdaliśmy sobie sprawę, właściwości te nie były naprawdę odpowiednie nazwy, więc ich zmianie:

public class TraversalZones 
{ 
    public int Left { get; set; } 
    public int Top { get; set; } 
}​ 

Zmień nazwę refactored właściwie w całym projekcie , ale wiem, że automatyczne migracje nie są wystarczająco inteligentne, aby wykryć te jawne nazwy w IDE, więc najpierw sprawdziłem, czy jedyną oczekującą migracją była zmiana nazwy kolumny:

update-database -f -script 

Na pewno po prostu pokazał, że SQL upuszcza niskie i wysokie wartości oraz dodaje lewy i górny. I dodaje ręczną migrację:

add-migration RenameColumns_TraversalZones_LowHigh_LeftTop 

I stała się wygenerowany kod po prostu:

public override void Up() 
{ 
    RenameColumn("TraversalZones", "Low", "Left"); 
    RenameColumn("TraversalZones", "High", "Top"); 
} 

public override void Down() 
{ 
    RenameColumn("TraversalZones", "Left", "Low"); 
    RenameColumn("TraversalZones", "Top", "High"); 
} 

Potem zaktualizowała DB:

update-database -verbose 

I got 2 Zmienia nazwę kolumny , tak jak się spodziewałem.

Kilka migracji później wykonałem kopię zapasową Produkcji i przywróciłem ją do lokalnego DB, aby przetestować kod na tym DB. Ta DB miał tabelę TraversalZones już utworzony w niej, ze starymi nazwami kolumn (niskie i wysokie) I oczywiście zaczął aktualizując go:

update-database -f -verbose 

I Zmień nazwę komendy pojawił się na wyjściu - wszyscy pojawił dobrze:

EXECUTE sp_rename @objname = N'TraversalZones.Low', @newname = N'Left', @objtype = N'COLUMN' 
EXECUTE sp_rename @objname = N'TraversalZones.High', @newname = N'Top', @objtype = N'COLUMN' 
[Inserting migration history record] 

Następnie uruchomiłem mój kod, który wykluczył, że baza danych zmieniła się od ostatniego uruchomienia, i że powinienem uruchomić update-database ....

Więc wpadłem go ponownie:

update-database -f -verbose 

i jestem teraz zakleszczony na ten błąd:

No pending code-based migrations. Applying automatic migration: 
201212191601545_AutomaticMigration. 
ALTER TABLE [dbo].[TraversalZones] ADD [Left] [int] NOT NULL DEFAULT 0 
System.Data.SqlClient.SqlException (0x80131904): Column names in each table must be unique. Column name 'Left' in table 'dbo.TraversalZones' is specified more than once. 
    at System.Data.SqlClient.SqlConnection.OnError(SqlException exception, Boolean breakConnection, Action`1 wrapCloseInAction) 
    at System.Data.SqlClient.SqlInternalConnection.OnError(SqlException exception, Boolean breakConnection, Action`1 wrapCloseInAction) 
    at System.Data.SqlClient.TdsParser.ThrowExceptionAndWarning(TdsParserStateObject stateObj, Boolean callerHasConnectionLock, Boolean asyncClose) 
    at System.Data.SqlClient.TdsParser.TryRun(RunBehavior runBehavior, SqlCommand cmdHandler, SqlDataReader dataStream, BulkCopySimpleResultSet bulkCopyHandler, TdsParserStateObject stateObj, Boolean& dataReady) 
    at System.Data.SqlClient.SqlCommand.RunExecuteNonQueryTds(String methodName, Boolean async, Int32 timeout) 
    at System.Data.SqlClient.SqlCommand.InternalExecuteNonQuery(TaskCompletionSource`1 completion, String methodName, Boolean sendToPipe, Int32 timeout, Boolean asyncWrite) 
    at System.Data.SqlClient.SqlCommand.ExecuteNonQuery() 
    at System.Data.Entity.Migrations.DbMigrator.ExecuteSql(DbTransaction transaction, MigrationStatement migrationStatement) 
    at System.Data.Entity.Migrations.Infrastructure.MigratorLoggingDecorator.ExecuteSql(DbTransaction transaction, MigrationStatement migrationStatement) 
    at System.Data.Entity.Migrations.DbMigrator.ExecuteStatements(IEnumerable`1 migrationStatements) 
    at System.Data.Entity.Migrations.Infrastructure.MigratorBase.ExecuteStatements(IEnumerable`1 migrationStatements) 
    at System.Data.Entity.Migrations.DbMigrator.ExecuteOperations(String migrationId, XDocument targetModel, IEnumerable`1 operations, Boolean downgrading, Boolean auto) 
    at System.Data.Entity.Migrations.DbMigrator.AutoMigrate(String migrationId, XDocument sourceModel, XDocument targetModel, Boolean downgrading) 
    at System.Data.Entity.Migrations.Infrastructure.MigratorLoggingDecorator.AutoMigrate(String migrationId, XDocument sourceModel, XDocument targetModel, Boolean downgrading) 
    at System.Data.Entity.Migrations.DbMigrator.Upgrade(IEnumerable`1 pendingMigrations, String targetMigrationId, String lastMigrationId) 
    at System.Data.Entity.Migrations.Infrastructure.MigratorLoggingDecorator.Upgrade(IEnumerable`1 pendingMigrations, String targetMigrationId, String lastMigrationId) 
    at System.Data.Entity.Migrations.DbMigrator.Update(String targetMigration) 
    at System.Data.Entity.Migrations.Infrastructure.MigratorBase.Update(String targetMigration) 
    at System.Data.Entity.Migrations.Design.ToolingFacade.UpdateRunner.RunCore() 
    at System.Data.Entity.Migrations.Design.ToolingFacade.BaseRunner.Run() 
ClientConnectionId:c40408ee-def3-4553-a9fb-195366a05fff 
Column names in each table must be unique. Column name 'Left' in table 'dbo.TraversalZones' is specified more than once.​ 

więc wyraźnie Migracje jest zdezorientowany, czy kolumna „Lewy” wciąż musi dokonać do tego stołu; Zakładam, że RenameColumn pozostawiłoby rzeczy we właściwym stanie, ale wydaje się, że tak nie jest.

Kiedy zrzucić co to próbuje zrobić z update-database -f -script, mam to staramy się robić dokładnie to, co by to zrobić jeśli instrukcja migracji nie były tam:

ALTER TABLE [dbo].[TraversalZones] ADD [Left] [int] NOT NULL DEFAULT 0 
ALTER TABLE [dbo].[TraversalZones] ADD [Top] [int] NOT NULL DEFAULT 0 
DECLARE @var0 nvarchar(128) 
SELECT @var0 = name 
FROM sys.default_constraints 
WHERE parent_object_id = object_id(N'dbo.TraversalZones') 
AND col_name(parent_object_id, parent_column_id) = 'Low'; 
IF @var0 IS NOT NULL 
    EXECUTE('ALTER TABLE [dbo].[TraversalZones] DROP CONSTRAINT ' + @var0) 
ALTER TABLE [dbo].[TraversalZones] DROP COLUMN [Low] 
DECLARE @var1 nvarchar(128) 
SELECT @var1 = name 
FROM sys.default_constraints 
WHERE parent_object_id = object_id(N'dbo.TraversalZones') 
AND col_name(parent_object_id, parent_column_id) = 'High'; 
IF @var1 IS NOT NULL 
    EXECUTE('ALTER TABLE [dbo].[TraversalZones] DROP CONSTRAINT ' + @var1) 
ALTER TABLE [dbo].[TraversalZones] DROP COLUMN [High] 
INSERT INTO [__MigrationHistory] ([MigrationId], [Model], [ProductVersion]) VALUES ('201212191639471_AutomaticMigration', 0x1F8B08000...000, '5.0.0.net40') 

Wydaje się to być błąd w Migracje.

Odpowiedz

15

Rozwiązaniem, oczywiście, jest to:

update-database -f -script 

której można zobaczyć wyniki w moim pytaniu. Potem wyrzuciłem wszystko ze skryptu, ale ostatnią linię, i uruchomiłem to w stosunku do bazy danych, aby powiadomić Migracje: już zmieniliśmy nazwę tej kolumny, wycinamy ją.

Mogę teraz kontynuować pracę z tym kopią bazy danych, ale obawiam się, że każda migracja z kopiami produkcji (do czasu migracji samej Produkcji) będzie nadal powodować ten problem. Jak mogę rozwiązać ten problem poprawnie bez tego obejścia?

Aktualizacja

Było to w rzeczywistości problem w każdym innym przypadku, włącznie z produkcją. Brudnym rozwiązaniem było wygenerowanie skryptu SQL (update-database -f -script) po zatwierdzeniu wygenerowanej wersji i wersji stałej.

Nieco czystsze rozwiązanie jest do podjęcia SQL ze skryptu, dodać ręczną migrację i zmienić zawartość z maksymalnie prosto:

public void Up() 
{ 
    Sql("...That SQL you extracted from the script..."); 
} 

To zapewni innych środowisk działających tę migrację zrobić tak właśnie sposób zamierzony.

Testowanie jest to trochę skomplikowane, więc można podejść do niego w ten sposób:

  1. tworzenia kopii zapasowych db na wszelki wypadek.
  2. Uruchom SQL. Jeśli działa poprawnie, odłóż SQL na bok.
  3. Dodaj migrację ręczną i wymaż wszystko w metodzie Up(). Pozostaw to całkowicie puste.
  4. Uaktualnij bazę danych -f
  5. Teraz zmień metodę Up(), dodając Sql("..."); wywołując SQL, który odłożyłeś.

Teraz db jest aktualny bez uruchamiania SQL dwukrotnie, a inne środowiska otrzymują wyniki tego SQL.

Powiązane problemy