这两个实体是一对多关系(由代码第一个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如何知道子节点的状态(新增/删除/修改)呢?

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


当前回答

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知道要添加哪些来更新哪些。

其他回答

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

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 && c.Id != default(int))
                .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可以接受任何对象,并根据属性名将属性值映射到附加的实体。如果模型中的属性名称与实体中的名称不同,则不能使用此方法,必须逐个分配值。

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知道要添加哪些来更新哪些。

考虑使用https://github.com/WahidBitar/EF-Core-Simple-Graph-Update。 这对我来说很有效。

这个库很简单,实际上只有一个扩展方法

T InsertUpdateOrDeleteGraph<T>(this DbContext context,
 T newEntity, T existingEntity)

https://github.com/WahidBitar/EF-Core-Simple-Graph-Update/blob/master/src/Diwink.Extensions.EntityFrameworkCore/DbContextExtensions.cs#L34

与这个问题的大多数答案相比,它是通用的(不使用硬编码的表名,可以用于不同的模型), 并包括针对不同模型更改的单元测试。 作者及时回应报告的问题。

这不是最优雅的方法,但很有效。干杯!

var entity = await context.Entities.FindAsync(id);

var newEntity = new AmazingEntity() {
  p1 = child1
  p2 = child2
  p3 = child3.child4 //... nested collections
};

if (entity != null) 
{
  db.Entities.Remove(entity);
}

db.Entities.Add(newEntity);

await db.SaveChangesAsync();

记住去掉PK。

var child4 = Tools.CloneJson(deepNestedElement);
child4.id = 0;
child3.Add(child4);


public static class Tools
{
  public static JsonSerializerSettings jsonSettings = new JsonSerializerSettings {
    ObjectCreationHandling = ObjectCreationHandling.Replace,
    ReferenceLoopHandling = ReferenceLoopHandling.Ignore
  }; 

  public static string JSerialize<T>(T source) {       
    return JsonConvert.SerializeObject(source, Formatting.Indented, jsonSettings);
  }

  public static T JDeserialize<T>(string source) {       
    return JsonConvert.DeserializeObject<T>(source, jsonSettings);
  }

  public static T CloneJson<T>(this T source)
  { 
    return CloneJson<T, T>(source);
  }

  public static TOut CloneJson<TIn, TOut>(TIn source)
  { 
    if (Object.ReferenceEquals(source, null))      
      return default(TOut);      
    return JDeserialize<TOut>(JSerialize(source));
  }
}

只是概念验证控制器。UpdateModel不能正常工作。

完整的课堂:

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;
    }

}