EF5代码优先迁移:使用RenameColumn后,"每个表中的列名必须是唯一的"错误

Chr*_*ini 16 entity-framework ef-code-first ef-migrations entity-framework-5

我们正在使用Entity Framework 5.0 Code First和Automatic Migrations.

我有一个这样的课:

public class TraversalZones
{
    public int Low { get; set; }
    public int High { get; set; }
}?
Run Code Online (Sandbox Code Playgroud)

然后我们意识到这些属性并不是正确的名称,所以我们改变了它们:

public class TraversalZones
{
    public int Left { get; set; }
    public int Top { get; set; }
}?
Run Code Online (Sandbox Code Playgroud)

重命名在整个项目中正确重构,但我知道自动迁移不够智能,无法在IDE中获取这些显式重命名,因此我首先检查以验证此列重命名的唯一挂起迁移:

update-database -f -script
Run Code Online (Sandbox Code Playgroud)

果然它只是显示SQL下降低和高并添加Left和Top.然后我添加了手动迁移:

add-migration RenameColumns_TraversalZones_LowHigh_LeftTop
Run Code Online (Sandbox Code Playgroud)

并将生成的代码修复为:

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

public override void Down()
{
    RenameColumn("TraversalZones", "Left", "Low");
    RenameColumn("TraversalZones", "Top", "High");
}
Run Code Online (Sandbox Code Playgroud)

然后我更新了db:

update-database -verbose
Run Code Online (Sandbox Code Playgroud)

并获得了2列重命名,就像我期待的那样.

稍后我进行了几次迁移备份生产并将其恢复到本地数据库以测试此数据库上的代码.这个数据库已经在其中创建了TraversalZones表,旧的列名称(低和高)我当然首先更新它:

update-database -f -verbose
Run Code Online (Sandbox Code Playgroud)

并且重命名命令出现在输出中 - 一切都很好:

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]
Run Code Online (Sandbox Code Playgroud)

然后我运行了我的代码,它错误地告诉我自上次运行以来数据库已经改变了,我应​​该运行update-database....

所以我又跑了一次:

update-database -f -verbose
Run Code Online (Sandbox Code Playgroud)

我现在坚持这个错误:

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.?
Run Code Online (Sandbox Code Playgroud)

因此,很明显,迁移对于"Left"列是否仍需要进入此表格感到困惑; 我会假设RenameColumn会将事物保持在适当的状态,但它似乎没有.

当我将它试图做的事情转移到a时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')
Run Code Online (Sandbox Code Playgroud)

这似乎是迁移中的一个错误.

Chr*_*ini 17

显然,解决方法是:

update-database -f -script
Run Code Online (Sandbox Code Playgroud)

您可以在我的问题中看到结果.然后我从脚本中抛出了所有内容,但最后一行,然后针对数据库运行,让Migrations知道:我们已经重命名了该列,将其删除.

我现在可以继续使用数据库副本,但我担心每次迁移生产副本(直到生产本身已迁移)都会遇到此问题.如果没有此解决方法,我该如何妥善解决?

更新

这实际上是包括Production在内的所有其他实例中的问题.update-database -f -script在提交生成的版本和固定版本之后,脏解决方案是生成SQL脚本().

一个稍微简洁的解决方案是从脚本中获取SQL,添加手动迁移,并将Up的内容更改为:

public void Up()
{
    Sql("...That SQL you extracted from the script...");
}
Run Code Online (Sandbox Code Playgroud)

这将确保运行此迁移的其他环境完全按照您的预期方式执行.

测试这个有点棘手,所以你可以这样做:

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

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