14

我们使用实体框架5.0代码优先和自动迁移。使用RenameColumn后EF5代码优先迁移:“每个表中的列名必须是唯一的”错误

我有一个类,像这样:

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; } 
}​ 

整个项目正常重构重命名,但我知道Automatic Migrations不够聪明,无法在IDE中接受这些显式重命名,所以我首先检查以确认唯一的未决迁移是此列重命名:

update-database -f -script 

果然,它只是显示SQL降低和高,并添加左和顶。然后我添加了一个手动迁移:

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

我然后更新分贝:

update-database -verbose 

和GOT 2个重命名,就像我期待的那样。

几次迁移后来我备份生产并将其还原到本地数据库以测试此数据库上的代码。该DB曾在它已经创建了TraversalZones表,与旧的列名(高)当然,我开始通过更新它:

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') 

这似乎是一个错误迁移。

回答

15

解决办法,很明显,是这样的:

update-database -f -script 

,你可以看到在我的问题的结果。然后,我抛弃了脚本中的所有内容,但是将最后一行的内容抛到了数据库上,让Migrations知道:我们已经重命名了该列,将其删除。

我现在可以继续这个数据库的副本,但我担心每次迁移对生产副本(直到生产本身已被迁移)将继续存在此问题。如何在没有此解决方法的情况下正确解决此问题?

更新

这实际上在所有其他情况下,包括生产的问题。肮脏的解决方案是在提交生成的版本和固定版本后生成SQL脚本(update-database -f -script)。

稍微清洁的解决方案是采取SQL从脚本,添加手动迁移和改变最多的简单内容:

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

这将确保运行此迁移的其他环境这样做恰恰是你想要的方式。

测试,这是一个有点棘手,所以你可以用这种方式来解决:

  1. 备份你的数据库,以防万一。
  2. 运行SQL。如果它正常工作,请将SQL放在一边。
  3. 添加手动迁移并清除Up()方法中的所有内容。将它完全清空。
  4. 运行update-database -f
  5. 现在通过添加Sql("...");来调用您预留的SQL来修改Up()方法。

现在您的数据库是最新的,无需两次运行SQL,其他环境将获得该SQL的结果。

相关问题