Entity Framework 4でDDDリポジトリパターンに従おうとしていますが、集約ルートのコレクションプロパティへの変更を保存する際に問題が発生します。以下の私のクラスを考えてみましょう。Itemは、SubItemエンティティのコレクションを含む集約ルートです。
public class Item
{
public int ItemId { get; set; }
public string Name { get; set; }
public ICollection<SubItem> SubItems { get; private set; }
public Item()
{
this.SubItems = new HashSet<SubItem>();
}
}
public class SubItem
{
public int ItemId { get; set; }
public int SubItemId { get; set; }
public string Name { get; set; }
}
次に、集約ルートクラスのリポジトリインターフェイスを定義しました
public interface IItemRespository
{
Item Get(int id);
void Add(Item i);
void Save(Item i);
}
これが、EFマッピングを設定するDbContextクラスです。
public class ItemContext : System.Data.Entity.DbContext
{
protected override void OnModelCreating(System.Data.Entity.DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Item>().HasKey(i => i.ItemId);
modelBuilder.Entity<Item>().Property(i => i.Name);
modelBuilder.Entity<Item>().HasMany(i => i.SubItems)
.WithRequired()
.HasForeignKey(si => si.ItemId);
modelBuilder.Entity<SubItem>().HasKey(i => i.SubItemId);
modelBuilder.Entity<SubItem>().Property(i => i.Name);
}
}
最後に、DBContextを使用したIRepositoryの実装を示します。
public class Repository : IItemRespository
{
public void Save(Item i)
{
using (var context = new ItemContext())
{
context.Set<Item>().Attach(i);
context.SaveChanges();
}
}
public Item Get(int id)
{
using (var context = new ItemContext())
{
var result = (from x in context.Set<Item>() where x.ItemId == id select x).FirstOrDefault();
return result;
}
}
public void Add(Item i)
{
using (var context = new ItemContext())
{
context.Set<Item>().Add(i);
context.SaveChanges();
}
}
}
次のコードは、新しいアイテムを作成し、それをリポジトリに追加し、いくつかの新しいサブアイテムを追加してから、変更を保存します。
IItemRespository repo = new Repository();
//Create a new Item
Item parent = new Item() { Name = "Parent" };
repo.Add(parent);
//A long period of time may pass .. . .
//later add sub items
parent.SubItems.Add(new SubItem() { Name = "Child 1" });
parent.SubItems.Add(new SubItem() { Name = "Child 2" });
parent.SubItems.Add(new SubItem() { Name = "Child 3" });
//save the added sub items
repo.Save(parent);
Save()メソッドがアイテムをコンテキストにアタッチしようとすると、次の例外が発生します。
参照整合性制約違反が発生しました:参照制約を定義するプロパティ値が、関係のプリンシパルオブジェクトと依存オブジェクトの間で一貫していません。
リポジトリ内のメソッドごとに新しいコンテキストを作成していることに気付きました。これは意図的なものです。アイテムが追加されてから後で編集されるまでに長い時間がかかる場合があります。コンテキストまたはデータベース接続をずっと開いたままにしたくありません。
以下のコードのように、サブアイテムを追加する前にnewItemを2番目のコンテキストにアタッチすると、機能します。
//Create a new item
Item newItem = new Item() { Name = "Parent" };
using (ItemContext context1 = new ItemContext())
{
//Create a new aggrgate
context1.Set<Item>().Add(newItem);
context1.SaveChanges();
}
//Long period of time may pass
using (ItemContext context2 = new ItemContext())
{
context2.Set<Item>().Attach(newItem);
newItem.Name = "Edited Name";
newItem.SubItems.Add(new SubItem() { Name = "Child 1" });
newItem.SubItems.Add(new SubItem() { Name = "Child 2" });
newItem.SubItems.Add(new SubItem() { Name = "Child 3" });
context2.SaveChanges();
}
ただし、リポジトリパターンに忠実でありたい場合、Itemを編集するコードは、リポジトリの動作やItemContextクラスについて何も知らないはずです。単に集約ルートエンティティに変更を加え、リポジトリのSave()メソッドを介してそれらの変更を保存できる必要があります。
では、Item.SubItemsへの変更が正しく保存されるようにSave()メソッドを変更するにはどうすればよいですか?