【问题标题】:EF5 Code First Migrations: "Column names in each table must be unique" error after using RenameColumnEF5 代码优先迁移:使用 RenameColumn 后出现“每个表中的列名必须唯一”错误
【发布时间】:2012-12-07 01:43:43
【问题描述】:

我们正在使用 Entity Framework 5.0 Code First 和自动迁移。

我有这样的课:

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

​ 然后我更新了数据库:

update-database -verbose

并得到了 2 个列重命名,正如我所期望的那样。

几次迁移之后,我备份了 Production 并将其还原到本地 DB 以测试该 DB 上的代码。这个数据库已经在其中创建了 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.​

因此,显然 Migrations 对“Left”列是否仍需要进入此表感到困惑;我会假设 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')

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

【问题讨论】:

    标签: entity-framework ef-code-first entity-framework-5 entity-framework-migrations


    【解决方案1】:

    显然,解决方法是这样的:

    update-database -f -script
    

    您可以在我的问题中看到结果。然后我把脚本中的所有内容都扔掉了,除了最后一行,然后在数据库上运行它,让 Migrations 知道:我们已经重命名了那个列,把它删掉。

    我现在可以继续数据库副本,但我担心每次针对生产副本的迁移(直到迁移生产本身)都会继续出现此问题。如果没有此解决方法,我该如何正确解决此问题?

    更新

    这实际上在包括生产在内的所有其他实例中都是一个问题。肮脏的解决方案是在提交生成的版本和修复的版本之后生成一个 SQL 脚本 (update-database -f -script)。

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

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

    这将确保运行此迁移的其他环境完全按照您的预期进行。

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

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

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-03-08
      • 2014-02-03
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-08-21
      相关资源
      最近更新 更多