web-dev-qa-db-ja.com

Code First Migrationで外部キーを追加する

移行を追加した後でデータベースを更新しようとすると、エラーが発生します。

これは移行前の私のクラスです

public class Product
{
    public Product() { }

    public int ProductId { get; set; } 
    public string Name { get; set; }
    public decimal Price { get; set; }
    public bool Istaxable { get; set; }
    public string DefaultImage { get; set; }
    public IList<Feature> Features { get; set; }
    public IList<Descriptor> Descriptors { get; set; }
    public IList<Image> Images { get; set; }
    public IList<Category> Categories { get; set; }
}


public class Feature
{
    public int Id { get; set; }
    public string Title { get; set; }
    public string Description { get; set; }

}

次に、Featureクラスに外部キーを追加し、クラスを次のようにリファクタリングします。

public class Product
{
    public Product() { }

    public int ProductId { get; set; } 
    public string Name { get; set; }
    public decimal Price { get; set; }
    public bool Istaxable { get; set; }
    public string DefaultImage { get; set; }
    public IList<Feature> Features { get; set; }
    public IList<Descriptor> Descriptors { get; set; }
    public IList<Image> Images { get; set; }
    public IList<Category> Categories { get; set; }
}

public class Feature
{
    public int Id { get; set; }
    public string Title { get; set; }
    public string Description { get; set; }
    public string Image { get; set; }
    public string VideoLink { get; set; }

    public int ProductId { get; set; }
    public Product Product { get; set; }
}

Add-Migrationコマンドを使用して移行を追加しました。私はUpdate-Databaseコマンドを追加しました、そしてここに私が得たものがあります:

ALTER TABLEステートメントがFOREIGN KEY制約「FK_dbo.ProductFeatures_dbo.Products_ProductId」と競合しました。データベース「CBL」、テーブル「dbo.Products」、列「ProductId」で競合が発生しました

この問題を解決して、移行を通常の状態に戻すにはどうすればよいですか?

19
user1714324

この問題を解決する鍵は、移行を2つの移行に分割することです。最初に、null許容フィールドを追加し、データを入力します。次に、フィールドを必須の外部キーにします。

最初の移行

  1. 新しいプロパティをnull許容型としてクラスに追加します(例:int?)

    public class MyOtherEntity
    {
        public int Id { get; set; }
    }
    
    public class MyEntity
    {
        ...
        // New reference to MyOtherEntity
        public int? MyOtherEntityId { get; set; }
        ...
    }
    
  2. 移行を作成します。注:移行名は重要ではありませんが、「AddBlogPosts1」のようなものを使用すると読みやすくなります。

    > add-migration AddMyEntityMyOtherEntity1
    
  3. これは、次のような移行の足場になるはずです。

    public partial class AddMyTableNewProperty1 : DbMigration
    {
        public override void Up()
        {
            AddColumn("dbo.MyEntity", "MyOtherEntityId", c => c.Int());
        }
        public override void Down()
        {
            DropColumn("dbo.MyEntity", "MyOtherEntityId");
        }
    }
    
  4. 次に、生成されたマイグレーションを手動で編集して、新しいフィールドのデフォルト値を追加します。最も簡単なケースは、デフォルト値が不変である場合です。必要に応じて、SQLにロジックを追加できます。この例では、すべてのMyEntityインスタンスがID 1の同じMyOtherEntityインスタンスを指していると想定しています。

    public partial class AddMyTableNewProperty1 : DbMigration
    {
        public override void Up()
        {
            AddColumn("dbo.MyEntity", "MyOtherEntityId", c => c.Int());
    
            // ADD THIS BY HAND
            Sql(@"UPDATE dbo.MyEntity SET MyOtherEntityId = 1
                  where MyOtherEntity IS NULL");
        }
        public override void Down()
        {
            DropColumn("dbo.MyEntity", "MyOtherEntityId");
        }
    }
    
  5. データベースを更新する

    > update-database
    

2回目の移行

  1. MyEntityクラスに戻り、必須の外部キーを表すように新しいプロパティを変更します。

    public class MyEntity
    {
        ...
        // Change the int? to int to make it mandatory
        public int MyOtherEntityId { get; set; }
    
        // Create a reference to the other entity
        public virtual MyOtherEntity MyOtherEntity { get; set; }
        ...
    }
    
  2. 別の移行を作成する

    > add-migration AddMyEntityMyOtherEntity2
    
  3. これにより、次のような移行が作成されます。

    public partial class AddMyEntityMyOtherEntity2: DbMigration
    {
        public override void Up()
        {
            AlterColumn("dbo.MyEntity", "MyOtherEntityId", c => c.Int(nullable: false));
            CreateIndex("dbo.MyEntity", "MyOtherEntityId");
            AddForeignKey("dbo.MyEntity", "MyOtherEntityId", "dbo.MyOtherEntity", "Id");
        }
        public override void Down()
        {
            DropForeignKey("dbo.MyEntity", "MyOtherEntityId", "dbo.MyOtherEntity");
            DropIndex("dbo.MyEntity", new[] { "MyOtherEntityId" });
            AlterColumn("dbo.MyEntity", "MyOtherEntityId", c => c.Int());
        }
    }
    
  4. データベースを更新する

    > update-database
    

その他の注意事項

  1. この手法は、アプリケーションの起動時に適用される移行で機能します。
  2. SQLの新しい列により複雑なマッピングを追加することは可能ですが、ここでは説明しません。
41
chiefgeek157

今日、この問題に遭遇しました。これが私がそれをどのように扱ったかです。私の場合、FKプロパティをnullにできるようにする必要がありましたが、これは大した問題ではありませんでした。

POCOに変更を加え、移行を生成してから、以下を移行に追加しました。

public partial class LineItemProductionHistory_v4 : DbMigration
{
    public override void Up()
    {
        AddColumn("LineItemProductionHistories","TempLineItemId",c=>c.Int());
        Sql("Update LineItemProductionHistories Set TempLineItemId = LineItem_Id");
       .
       . Generated code
       .
        Sql("Update LineItemProductionHistories Set LineItemId = TempLineItemId");
        DropColumn("LineItemProductionHistories", "TempLineItemId");
    }
}

私は一時的にすべての外部キーを保存し、移行に追加/ドロップを行わせてから、新しく作成されたFKに外部キーを戻します。

6
Jason Massey

簡単な答えは-最初にProductIdにnull可能な列を追加し、次に既存のすべての行にデフォルト値を設定してから、将来の挿入のために列をnull以外に設定し(必要な場合)、最後に外部キー制約を追加します。

私はこれについて、完全なソースコードを含めて長いブログ投稿を書いた- http://nodogmablog.bryanhogan.net/2015/04/entity-framework-non-null-foreign-key-migration/

2
Bryan

これは、すでにデータが含まれているテーブルのnull不可の列に外部キー制約を追加しようとしたときに発生する可能性があります。テーブルにデータが含まれている場合は、まずそれらを削除してから、データベースの更新を再試行してください。

2
ixzo

これは古い問題ですが、現在、個別の移行を作成する必要はなく、この問題はいくつかの手順で解決できます。

  1. エンティティの変更を使用してAdd-Migrationを実行し(新しいnull不可の参照プロパティを追加、この場合はProductId)、新しい移行クラスを足場します。
  2. 新しく追加された移行を変更して、null可能ではなくnull可能列(nullable:true)を作成する
  3. AddColumnの下で、必要に応じて列の値を設定するSql addコマンド
  4. 以下は、意図したとおりに列をnullにできないようにするAlterColumnコマンドを追加します

上記の例では、次のようになります。

    public override void Up()
    {
        AddColumn("dbo.ProductFeatures", "ProductId", c => c.Int(nullable: true));
        Sql("UPDATE [dbo].[ProductFeatures] SET ProductId = (SELECT TOP 1 [Id] FROM [dbo].[Products])");
        AlterColumn("dbo.ProductFeatures", "ProductId", c => c.Int(nullable: false));
        CreateIndex("dbo.ProductFeatures", "ProductId");
        AddForeignKey("dbo.ProductFeatures", "ProductId", "dbo.Products", "Id");
    }
1
too