web-dev-qa-db-ja.com

EF5コードの最初の移行:RenameColumnを使用した後の「各テーブルの列名は一意である必要があります」エラー

Entity Framework5.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つの列の名前を変更しました。

数回の移行の後、本番環境をバックアップしてローカルDBに復元し、この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.​

したがって、「Left」列をこのテーブルに追加する必要があるかどうかについて、Migrationsは明らかに混乱しています。 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')

これは移行のバグのようです。

16
Chris Moschini

回避策は、明らかにこれです:

_update-database -f -script
_

あなたは私の質問で結果を見ることができます。次に、スクリプトから最後の行以外のすべてを投げ、それをDBに対して実行して、移行に通知しました。すでにその列の名前を変更し、切り取りました。

これで、データベースのthisコピーを続行できますが、Productionのコピーに対するすべての移行(Production自体が移行されるまで)が維持されるのではないかと心配しています。この問題があります。この回避策なしでこれを適切に解決するにはどうすればよいですか?

更新

これは実際、本番環境を含む他のすべてのインスタンスで問題でした。汚い解決策は、生成されたバージョンと修正されたバージョンをコミットした後、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. Update-database-fを実行します
  5. 次に、取っておいたSQLを呼び出すSql("...");を追加して、Up()メソッドを変更します。

これで、SQLを2回実行しなくてもデータベースが最新になり、他の環境でそのSQLの結果が得られます。

17
Chris Moschini