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が言っていることを誤解していませんか?
実際に説明どおりに機能しますが、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ステートメントがデータベースに書き込まれます。
@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エンティティの状態を削除済みに設定します(そのようにしている場合)。