How to add/update child entities when updating a parent entity in EF

asp.net-mvc asp.net-web-api c# entity-framework

Question

The relationship between the two parties is one to many (built by code first 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; }
}

I have actions to create a parent entity (which is working well) and update a parent entity in my WebApi controller (which has some problem). The update procedure appears to be:

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

I currently have two thoughts:

  1. Obtain the tracked parent entityexisting by model.Id and place values inmodel individually to the thing. This sounds ridiculous. And inmodel.Children I am unsure of which children are changed or new (or even deleted).

  2. New parent entity creation viamodel and saved it after attaching it to the DbContext. However, how does the DbContext know whether the children are new, added, deleted, or modified?

How should this feature be implemented correctly?

1
146
11/27/2014 5:17:22 PM

Accepted Answer

The only alternative is to load the object graph (parent and children) from the database and compare which children have been added, destroyed, or altered because the model that is submitted to the WebApi controller is removed from any entity-framework (EF) context. (Except if, during the detached state, you decide to track the changes using your own tracking system (in the browser or elsewhere), which is, in my opinion, more involved than what follows.) It might seem as follows:

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 may take any object and, using the property name, translates its values to the associated entity. You cannot use this method and must instead assign each value one at a time if the property names in your model and the entity have different names.

210
3/16/2017 4:46:53 AM

Popular Answer

I've been experimenting with something similar...

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

which you can summon by using a phrase like:

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

Unfortunately, this kind of fails if the child type also needs its collection properties modified. Consider attempting to resolve this by giving an IRepository (with fundamental CRUD functions) that would be in charge of independently invoking UpdateChildCollection. would make calls to the repo as opposed to direct DbContext calls. Entry.

I'm not sure what else to do with this problem but I have no idea how this will all work at scale.



Related Questions





Related

Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow
Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow