这两个实体是一对多关系(由代码第一个fluent api构建)。

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控制器中,我有创建父实体(工作正常)和更新父实体(有一些问题)的操作。更新操作如下所示:

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

目前我有两个想法:

获取一个被跟踪的父实体,命名为按模型存在的。Id,并将模型中的值逐个分配给实体。这听起来很愚蠢。在模型中。我不知道哪个子是新的,哪个子是修改的(甚至是删除的)。 通过模型创建一个新的父实体,并将其附加到DbContext并保存。但是DbContext如何知道子节点的状态(新增/删除/修改)呢?

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


当前回答

我一直在摆弄这样的东西……

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)

不幸的是,如果子类型上有集合属性也需要更新,这种方法就会失败。考虑通过传递一个IRepository(带有基本的CRUD方法)来解决这个问题,这个IRepository将负责自己调用UpdateChildCollection。调用repo,而不是直接调用DbContext.Entry。

不知道这将如何大规模地执行,但不确定还能做什么来解决这个问题。

其他回答

好男人。我曾经有过这个答案,但后来又失去了。当你知道有更好的方法,但却想不起来或找不到时,绝对是一种折磨!这很简单。我只是用多种方法测试了一下。

var parent = _dbContext.Parents
  .Where(p => p.Id == model.Id)
  .Include(p => p.Children)
  .FirstOrDefault();

parent.Children = _dbContext.Children.Where(c => <Query for New List Here>);
_dbContext.Entry(parent).State = EntityState.Modified;

_dbContext.SaveChanges();

你可以用一个新的列表替换整个列表!SQL代码将根据需要删除和添加实体。你不必为那件事操心。确保包含子集合,否则就没有骰子。好运!

我一直在摆弄这样的东西……

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)

不幸的是,如果子类型上有集合属性也需要更新,这种方法就会失败。考虑通过传递一个IRepository(带有基本的CRUD方法)来解决这个问题,这个IRepository将负责自己调用UpdateChildCollection。调用repo,而不是直接调用DbContext.Entry。

不知道这将如何大规模地执行,但不确定还能做什么来解决这个问题。

public async Task<IHttpActionResult> PutParent(int id, Parent parent)
        {
            if (!ModelState.IsValid)
            {
                return BadRequest(ModelState);
            }

            if (id != parent.Id)
            {
                return BadRequest();
            }

            db.Entry(parent).State = EntityState.Modified;

            foreach (Child child in parent.Children)
            {
                db.Entry(child).State = child.Id == 0 ? EntityState.Added : EntityState.Modified;
            }

            try
            {
                await db.SaveChangesAsync();
            }
            catch (DbUpdateConcurrencyException)
            {
                if (!ParentExists(id))
                {
                    return NotFound();
                }
                else
                {
                    throw;
                }
            }

            return Ok(db.Parents.Find(id));
        }

这就是我解决这个问题的方法。通过这种方式,EF知道要添加哪些来更新哪些。

如果你正在使用EntityFrameworkCore,你可以在你的控制器post动作中做以下事情(Attach方法递归地附加导航属性,包括集合):

_context.Attach(modelPostedToController);

IEnumerable<EntityEntry> unchangedEntities = _context.ChangeTracker.Entries().Where(x => x.State == EntityState.Unchanged);

foreach(EntityEntry ee in unchangedEntities){
     ee.State = EntityState.Modified;
}

await _context.SaveChangesAsync();

假设每个被更新的实体都设置了所有属性,并在来自客户端的post数据中提供。不会为实体的部分更新工作)。

您还需要确保为该操作使用了一个新的/专用的实体框架数据库上下文。

VB。NET开发人员使用这个通用子标记子状态,易于使用

注: PromatCon:实体对象 amList:要添加或修改的子列表 rList:要删除的子列表

updatechild(objCas.ECC_Decision, PromatCon.ECC_Decision.Where(Function(c) c.rid = objCas.rid And Not objCas.ECC_Decision.Select(Function(x) x.dcid).Contains(c.dcid)).toList)
Sub updatechild(Of Ety)(amList As ICollection(Of Ety), rList As ICollection(Of Ety))
        If amList IsNot Nothing Then
            For Each obj In amList
                Dim x = PromatCon.Entry(obj).GetDatabaseValues()
                If x Is Nothing Then
                    PromatCon.Entry(obj).State = EntityState.Added
                Else
                    PromatCon.Entry(obj).State = EntityState.Modified
                End If
            Next
        End If

        If rList IsNot Nothing Then
            For Each obj In rList.ToList
                PromatCon.Entry(obj).State = EntityState.Deleted
            Next
        End If
End Sub
PromatCon.SaveChanges()