如何在EF中更新父实体时添加/更新子实体

这两个实体是一对多的关系(由第一代stream利的API构build)。

public class Parent { public Parent() { this.Children = new List<Child>(); } public int Id { get; set; } public virtual ICollection<Child> Children { get; set; } } public class Child { public int Id { get; set; } public int ParentId { get; set; } public string Data { get; set; } } 

在我的WebApi控制器,我有行动来创build一个父实体(这是工作正常),并更新一个父实体(有一些问题)。 更新操作如下所示:

 public void Update(UpdateParentModel model) { //what should be done here? } 

目前我有两个想法:

  1. 获取由model.Id命名的existing的父实体,并将modelmodel.Id分配给实体。 这听起来很愚蠢。 而在model.Children我不知道哪个孩子是新的,哪个孩子被修改(甚至删除)。

  2. 通过model创build一个新的父实体,并将其附加到DbContext并保存。 但是DbContext如何知道儿童的状态(新增/删除/修改)?

什么是实现这个function的正确方法?

因为发布到WebApi控制器的模型从任何entity framework(EF)上下文中分离出来,所以唯一的select是从数据库中加载对象图(包括它的子对象的父对象),并比较哪些子对象被添加,删除或更新。 (除非你在分离状态(在浏览器或任何地方)跟踪你自己的跟踪机制的变化,在我看来这比以下更复杂)。它可能看起来像这样:

 public void Update(UpdateParentModel model) { var existingParent = _dbContext.Parents .Where(p => p.Id == model.Id) .Include(p => p.Children) .SingleOrDefault(); if (existingParent != null) { // Update parent _dbContext.Entry(existingParent).CurrentValues.SetValues(model); // Delete children foreach (var existingChild in existingParent.Children.ToList()) { if (!model.Children.Any(c => c.Id == existingChild.Id)) _dbContext.Children.Remove(existingChild); } // Update and Insert children foreach (var childModel in model.Children) { var existingChild = existingParent.Children .Where(c => c.Id == childModel.Id) .SingleOrDefault(); if (existingChild != null) // Update child _dbContext.Entry(existingChild).CurrentValues.SetValues(childModel); else { // Insert child var newChild = new Child { Data = childModel.Data, //... }; existingParent.Children.Add(newChild); } } _dbContext.SaveChanges(); } } 

...CurrentValues.SetValues可以接受任何对象,并根据属性名称将属性值映射到附属实体。 如果模型中的属性名称与实体中的名称不同,则不能使用此方法,并且必须逐个分配值。

我一直在搞这样的事情

 protected void UpdateChildCollection<Tparent, Tid , Tchild>(Tparent dbItem, Tparent newItem, Func<Tparent, IEnumerable<Tchild>> selector, Func<Tchild, Tid> idSelector) where Tchild : class { var dbItems = selector(dbItem).ToList(); var newItems = selector(newItem).ToList(); if (dbItems == null && newItems == null) return; var original = dbItems?.ToDictionary(idSelector) ?? new Dictionary<Tid, Tchild>(); var updated = newItems?.ToDictionary(idSelector) ?? new Dictionary<Tid, Tchild>(); var toRemove = original.Where(i => !updated.ContainsKey(i.Key)).ToArray(); var removed = toRemove.Select(i => DbContext.Entry(i.Value).State = EntityState.Deleted).ToArray(); var toUpdate = original.Where(i => updated.ContainsKey(i.Key)).ToList(); toUpdate.ForEach(i => DbContext.Entry(i.Value).CurrentValues.SetValues(updated[i.Key])); var toAdd = updated.Where(i => !original.ContainsKey(i.Key)).ToList(); toAdd.ForEach(i => DbContext.Set<Tchild>().Add(i.Value)); } 

你可以通过这样的方式调用:

 UpdateChildCollection(dbCopy, detached, p => p.MyCollectionProp, collectionItem => collectionItem.Id) 

不幸的是,如果有子types的集合属性也需要更新,那么这种情况就会发生。 考虑试图通过传递一个IRepository(具有基本的CRUD方法)来解决这个问题,它将负责自己调用UpdateChildCollection。 将调用回购,而不是直接调用DbContext.Entry。

不知道这将如何大规模执行,但不知道如何处理这个问题。

只是概念certificate Controler.UpdateModel将无法正常工作。

全class在这里 :

 const string PK = "Id"; protected Models.Entities con; protected System.Data.Entity.DbSet<T> model; private void TestUpdate(object item) { var props = item.GetType().GetProperties(); foreach (var prop in props) { object value = prop.GetValue(item); if (prop.PropertyType.IsInterface && value != null) { foreach (var iItem in (System.Collections.IEnumerable)value) { TestUpdate(iItem); } } } int id = (int)item.GetType().GetProperty(PK).GetValue(item); if (id == 0) { con.Entry(item).State = System.Data.Entity.EntityState.Added; } else { con.Entry(item).State = System.Data.Entity.EntityState.Modified; } } 

有几个项目可以让客户端和服务器之间的交互更容易,只要它涉及保存整个对象图。

这里有两个你想看看:

  • 可跟踪的实体
  • 微风#

上述两个项目在返回到服务器时都会识别断开连接的实体,检测并保存更改并返回到客户端受影响的数据。