我正在尝试使用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);
}
现在这是我的DbContext类,用于设置EF映射。
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
实现的IRepositorypublic 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();
}
}
}
以下代码创建一个新Item,将其添加到存储库,添加一些新的SubItems,然后保存更改。
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附加到第二个上下文,就像在下面的代码中一样,它可以工作。
//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()方法保存这些更改。
那么如何修改Save()方法以便正确保存对Item.SubItems的更改?
答案 0 :(得分:13)
您需要通过设置一些属性来帮助EF,以使其发挥作用。
当您创建新的子项时,您需要自己设置FK:
parent.SubItems.Add(new SubItem() { Name = "Child 1", ItemId = parent.ItemId});
parent.SubItems.Add(new SubItem() { Name = "Child 2", ItemId = parent.ItemId });
parent.SubItems.Add(new SubItem() { Name = "Child 3", ItemId = parent.ItemId });
然后在保存功能中,将项目添加或附加到您的上下文中:
public void Save(Item i)
{
using (var context = new ItemContext())
{
foreach (var subitem in i.SubItems)
{
if (subitem.SubItemId == 0)
context.Set<SubItem>().Add(subitem);
else
context.Set<SubItem>().Attach(subitem);
}
context.Set<Item>().Attach(i);
context.SaveChanges();
}
}
原因是,因为当您进行附加时,您的实体并未附加到上下文中,因此EF实际上并不知道实体的来源 - 它认为FK不是set(可能为0)是一个有效的状态 - 这是你的错误来自哪里。您需要首先附加子对象的原因是您实际上可以添加而不是附加。同样,由于附加子项时您的上下文不存在,因此EF不确定实体的来源,并假设0 PK正确,从而产生错误。
答案 1 :(得分:-1)
模型
public virtual ICollection<CostCenter> CostCenters { get; set; }
[NotMapped]
public int CostCenterId { get; set; }//Just to get item in view
创建
public ActionResult Create()
{
ViewBag.CostCenterId = new SelectList(db.CostCenters, "Id", "Name");
return View();
}
创建帖子
public ActionResult Create(Campaign campaign)
{
...
campaign.CostCenters.Add(db.CostCenters.FirstOrDefault(f => f.Id == campaign.CostCenterId);
return RedirectToAction("Index");
}
查看
<%: Html.LabelFor(model => model.CostCenterId ) %>
<div class="input-control">
<%: Html.DropDownList("CostCenterId ", String.Empty) %>
<%: Html.ValidationMessageFor(model => model.CostCenterId ) %>
</div>