EF5 Code First Migrations: "Имена столбцов в каждой таблице должны быть уникальными" после использования RenameColumn
Мы используем First Entity Framework 5.0 и автоматическую миграцию.
У меня был класс вроде:
public class TraversalZones
{
public int Low { get; set; }
public int High { get; set; }
}
Тогда мы поняли, что эти свойства на самом деле не являются правильными именами, поэтому мы изменили их:
public class TraversalZones
{
public int Left { get; set; }
public int Top { get; set; }
}
Переименовать рефакторинг правильно во всем проекте, но я знаю, что автоматические миграции не достаточно умны, чтобы забрать эти явные переименования в среде IDE, поэтому я сначала проверил, чтобы проверить, что только ожидающая миграция была переименована в этом столбце:
update-database -f -script
Конечно, это просто показало, что SQL понижает Low и High и добавляет Left и Top. Затем я добавил миграцию вручную:
add-migration RenameColumns_TraversalZones_LowHigh_LeftTop
И скорректированный сгенерированный код просто:
public override void Up()
{
RenameColumn("TraversalZones", "Low", "Left");
RenameColumn("TraversalZones", "High", "Top");
}
public override void Down()
{
RenameColumn("TraversalZones", "Left", "Low");
RenameColumn("TraversalZones", "Top", "High");
}
Затем я обновил db:
update-database -verbose
И получил 2 переименования столбцов, как и ожидал.
Несколько миграций позже я создал резервную копию Production и восстановил ее в локальной базе данных, чтобы протестировать код в этой БД. Эта БД имела уже созданную в ней таблицу TraversalZones со старыми именами столбцов (Low и High), я, конечно же, начал с ее обновления:
update-database -f -verbose
И команды переименования появились на выходе - все получилось хорошо:
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]
Затем я запустил свой код, и он ошибся, указав, что база данных изменилась с момента последнего запуска, и что я должен запустить update-database
....
Итак, я снова запустил его:
update-database -f -verbose
И вот теперь эта ошибка:
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.
Итак, очевидно, что Миграции смущены относительно того, должен ли столбец "Слева" поместить его в эту таблицу; Я бы предположил, что RenameColumn оставит вещи в правильном состоянии, но, похоже, это не так.
Когда я сбрасываю то, что он пытается сделать с update-database -f -script
, я пытаюсь сделать то, что он сделал бы, если бы миграция вручную не была:
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')
Это кажется ошибкой в Migrations.
Ответы
Ответ 1
Обходной путь, очевидно, таков:
update-database -f -script
Что вы можете увидеть в моем вопросе. Затем я выбросил все из script, но в последнюю строку, и запустил это для БД, чтобы сообщить Migrations: Мы уже переименовали этот столбец, отключили его.
Теперь я могу продолжить работу с этой копией базы данных, но я обеспокоен тем, что всякая миграция с копиями Production (пока сама передача не была перенесена) будет продолжать эту проблему. Как я могу разрешить это правильно без этого обхода?
Обновление
Это была проблема в каждом другом экземпляре, включая Production. Грязное решение заключалось в создании SQL script (update-database -f -script
) после выполнения сгенерированной версии и фиксированной версии.
Немного более чистое решение - взять SQL из script, добавить ручную миграцию и изменить содержимое до простого:
public void Up()
{
Sql("...That SQL you extracted from the script...");
}
Это обеспечит, чтобы другие среды, выполняющие эту миграцию, выполняли именно так, как вы планировали.
Тестирование это немного сложно, поэтому вы можете подойти так:
- Резервное копирование вашего db на всякий случай.
- Запустите SQL. Если он работает правильно, отложите SQL.
- Добавьте ручную миграцию и уничтожьте все в методе Up(). Оставьте его полностью пустым.
- Запустить update-database -f
- Теперь измените метод Up(), добавив
Sql("...");
вызов SQL, который вы отложили.
Теперь ваш db обновляется без запуска SQL дважды, а другие среды получают результаты этого SQL.