简体   繁体   English

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

[英]EF5 Code First Migrations: “Column names in each table must be unique” error after using RenameColumn

We're using Entity Framework 5.0 Code First and Automatic Migrations. 我们正在使用Entity Framework 5.0 Code First和Automatic Migrations。

I had a class like so: 我有一个这样的课:

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

Then we realized these properties weren't really the right names, so we changed them: 然后我们意识到这些属性并不是正确的名称,所以我们改变了它们:

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

The rename refactored properly throughout the project, but I know Automatic Migrations aren't smart enough to pick up these explicit renames in the IDE, so I first checked to verify the only pending migration was this column rename: 重命名在整个项目中正确重构,但我知道自动迁移不够智能,无法在IDE中获取这些显式重命名,因此我首先检查以验证此列重命名的唯一挂起迁移:

update-database -f -script

Sure enough it just showed the SQL dropping Low and High and adding Left and Top. 果然它只是显示SQL下降低和高并添加Left和Top。 I then added a manual migration: 然后我添加了手动迁移:

add-migration RenameColumns_TraversalZones_LowHigh_LeftTop

And fixed up the generated code to simply: 并将生成的代码修复为:

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

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

​ I then updated the db: 然后我更新了db:

update-database -verbose

And got 2 column renames, just like I was expecting. 并获得了2列重命名,就像我期待的那样。

Several migrations later I backed up Production and Restored it to a local DB to test the code on this DB. 稍后我进行了几次迁移备份生产并将其恢复到本地数据库以测试此数据库上的代码。 This DB had the TraversalZones table already created in it, with the old column names (Low and High) I of course began by updating it: 这个数据库已经在其中创建了TraversalZones表,旧的列名称(低和高)我当然首先更新它:

update-database -f -verbose

And the rename commands appeared in the output - all appeared well: 并且重命名命令出现在输出中 - 一切都很好:

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]

I then ran my code, and it errored out telling me the database had changed since last run, and that I should run update-database ... . 然后我运行了我的代码,它错误地告诉我自上次运行以来数据库已经改变了,我应​​该运行update-database ....

So I ran it again: 所以我又跑了一次:

update-database -f -verbose

And am now stuck on this error: 我现在坚持这个错误:

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.​

So, clearly Migrations is confused as to whether the column "Left" still needs to make it into this table; 因此,很明显,迁移对于“Left”列是否仍然需要进入此表格感到困惑; I would assume RenameColumn would leave things in the proper state, but it appears it has not. 我会假设RenameColumn会将事物保持在适当的状态,但它似乎没有。

When I dump what it's attempting to do to a update-database -f -script , I get it trying to do exactly what it would have done if the manual migration were not there: 当我将它正在尝试做的事情转储到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')

This appears to be a bug in Migrations. 这似乎是迁移中的一个错误。

The workaround, obviously, is this: 显然,解决方法是:

update-database -f -script

Which you can see the results of in my question. 您可以在我的问题中看到结果。 Then I tossed everything from the script but the last line, and ran that against the DB to let Migrations know: We already renamed that column, cut it out. 然后我从脚本中抛出了所有内容,但最后一行,然后针对数据库运行,让Migrations知道:我们已经重命名了该列,将其删除。

I can now proceed with this copy of the database, but I'm concerned every migration against copies of Production (until Production itself has been migrated) will keep having this issue. 我现在可以继续使用这个数据库副本了,但是我担心每次迁移生产副本(直到生产本身已经迁移)都会遇到这个问题。 How can I resolve this properly without this workaround? 如果没有此解决方法,我该如何妥善解决?

Update 更新

This was in fact an issue in every other instance including Production. 这实际上是包括Production在内的所有其他实例中的问题。 The dirty solution was to generate a SQL script ( update-database -f -script ), after committing the generated version and the fixed version. 在提交生成的版本和固定版本之后,脏解决方案是生成SQL脚本( update-database -f -script )。

A slightly cleaner solution is to take the SQL from the script, add a manual migration, and change the contents of Up to simply: 一个稍微简洁的解决方案是从脚本中获取SQL,添加手动迁移,并将Up的内容更改为:

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

This will ensure other environments running this migration do so precisely the way you intended. 这将确保运行此迁移的其他环境完全按照您的预期方式执行。

Testing this is a bit tricky so you can approach it this way: 测试这个有点棘手,所以你可以这样处理它:

  1. Backup your db just in case. 备份你的数据库以防万一。
  2. Run the SQL. 运行SQL。 If it works properly, set the SQL aside. 如果它正常工作,请将SQL放在一边。
  3. Add the manual migration and wipe out everything in the Up() method. 添加手动迁移并清除Up()方法中的所有内容。 Leave it completely empty. 把它完全清空。
  4. Run update-database -f 运行update-database -f
  5. Now modify the Up() method by adding the Sql("..."); 现在通过添加Sql("...");修改Up()方法Sql("..."); calling the SQL you set aside. 调用你预留的SQL。

Now your db is up to date without running the SQL twice, and other environments get the results of that SQL. 现在,您的数据库是最新的,无需运行SQL两次,其他环境可以获得该SQL的结果。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM