web-dev-qa-db-ja.com

エンティティフレームワーク:外部キーを無効にするようにカスケード削除を構成する方法

EntityFrameworkのドキュメントには、次の動作が可能であると記載されています。

依存エンティティの外部キーがnull可能である場合、Code Firstは関係にカスケード削除を設定しません。プリンシパルが削除されると、外部キーはnullに設定されます。

(- http://msdn.Microsoft.com/en-us/jj59162 から)

しかし、私はそのような振る舞いを達成することはできません。

次のエンティティをcode-firstで定義しています。

public class TestMaster
{
    public int Id { get; set; }
    public string Name { get; set; }        
    public virtual ICollection<TestChild> Children { get; set; }       
}

public class TestChild
{
    public int Id { get; set; }
    public string Name { get; set; }
    public virtual TestMaster Master { get; set; }
    public int? MasterId { get; set; }
}

Fluent APIマッピング構成は次のとおりです。

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        modelBuilder.Entity<TestMaster>()
                    .HasMany(e => e.Children)
                    .WithOptional(p => p.Master).WillCascadeOnDelete(false);

        modelBuilder.Entity<TestChild>()
                    .HasOptional(e => e.Master)
                    .WithMany(e => e.Children)
                    .HasForeignKey(e => e.MasterId).WillCascadeOnDelete(false);
    }

外部キーはnull可能で、ナビゲーションプロパティはオプションとしてマップされるため、MSDNの説明に従ってカスケード削除が機能することを期待します。つまり、すべての子のMasterIDを無効にしてから、Masterオブジェクトを削除します。

しかし、実際に削除しようとすると、FK違反エラーが発生します。

 using (var dbContext = new TestContext())
        {
            var master = dbContext.Set<TestMaster>().Find(1);
            dbContext.Set<TestMaster>().Remove(master);
            dbContext.SaveChanges();
        }

SaveChanges()の場合、次がスローされます。

System.Data.Entity.Infrastructure.DbUpdateException : An error occurred while updating the entries. See the inner exception for details.
----> System.Data.UpdateException : An error occurred while updating the entries. See the inner exception for details.
----> System.Data.SqlClient.SqlException : The DELETE statement conflicted with the REFERENCE constraint "FK_dbo.TestChilds_dbo.TestMasters_MasterId". The conflict occurred in database "SCM_Test", table "dbo.TestChilds", column 'MasterId'.
The statement has been terminated.

私は何か間違ったことをしているのですか、それともMSDNが言っていることを誤解していませんか?

30
Alex

実際に説明どおりに機能しますが、MSDNの記事では、親エンティティだけでなく、子がコンテキストにロードされた場合にのみ機能するも強調していません。したがって、Find(親のみをロードする)を使用する代わりに、Include(または子をコンテキストにロードする他の方法)を使用した積極的なロードを使用する必要があります。

using (var dbContext = new TestContext())
{
    var master = dbContext.Set<TestMaster>().Include(m => m.Children)
        .SingleOrDefault(m => m.Id == 1);
    dbContext.Set<TestMaster>().Remove(master);
    dbContext.SaveChanges();
}

これにより、マスターがデータベースから削除され、Childエンティティのすべての外部キーがnullに設定され、子のUPDATEステートメントがデータベースに書き込まれます。

44
Slauma

@Slaumaの素晴らしい回答をフォローした後も、OPと同じエラーが発生しました。

だから私ほどナイーブにならないで、以下の例は同じ結果になると思います。

dbCtx.Entry(principal).State = EntityState.Deleted;
dbCtx.Dependant.Where(d => d.PrincipalId == principalId).Load();

// code above will give error and code below will work on dbCtx.SaveChanges()

dbCtx.Dependant.Where(d => d.PrincipalId == principalId).Load();
dbCtx.Entry(principal).State = EntityState.Deleted;

最初に子をコンテキストにロードしますbeforeエンティティの状態を削除済みに設定します(そのようにしている場合)。

0
Quinton Smith