TheMixy
TheMixy

Reputation: 1316

Entity Framework Core Update child entities problem

In my ASP.NET Core 2.2. MVC project I have a master-detail form that I use for editing a PARENT table that also has a list of child entities (stored in CHILD table).

When I save the modifications on the form I first save the changes to the parent entity and then run the following method to update the corresponding child entities:

private void UpdateChildList(ParentModel model)
{
    // lists
    var listOld = _parents.Get(model.IdParent).ListChilds;
    var listNew = model.ListChilds;
    var listNewInt = from x in listNew select x.IdChild;

    // 1. remove deleted
    var deleteList = new List<int>();
    deleteList = listOld.Where(t => !listNewInt.Contains(t.IdChild)).Select(x => x.IdChild).ToList();

    foreach (var d in deleteList)
    {
        var item = listOld.Where(x => x.IdChild == l).First();
        _parents.DeleteChild(item);
    }

    // 2. update existing
    var list = from x in listNew
    where x.IdChild > 0
    select x;

    foreach (var item in list)
    {
        _parents.UpdateChild(item);
    }

    // 3. add new 
    list = from x in listNew
    where x.IdChild == 0
    select x;

    foreach (var item in list)
    {
        _parents.AddChild(item);
    }
}

1. deleting works OK & 3. adding works OK, but 2. UPDATING produces the following error:

InvalidOperationException: The instance of entity type 'Child' cannot be tracked because another instance with the same key value for {'IdChild'} is already being tracked. When attaching existing entities, ensure that only one entity instance with a given key value is attached. Consider using 'DbContextOptionsBuilder.EnableSensitiveDataLogging' to see the conflicting key values.

Note: I'm using service.AddScoped to add services in my Startup class.

EDIT: I'm adding the code from my HTTPPost (in controller):

[HttpPost]
public IActionResult Edit(ParentModel model)
{
    // valid check
    if (!ModelState.IsValid)
    {
        /// ... some lists for dropdowns here

        return View(model);
    }

    var item = _parents.Get(model.IdParent);

    item.Field1 = model.Field1;
    // ... and other fields

    // update parent
    _parents.Update(item);

    // update Child list
    UpdateChildList(model);

    return RedirectToAction("Index");

}

EDIT2: adding my code for delete/update/add child records

public void AddChild(Child item)
{
    _dbContext.Add(item);
    _dbContext.SaveChanges();
}

public void UpdateChild(Child item)
{
    _dbContext.Update(item);
    _dbContext.SaveChanges();
}

public void DeleteChild(Child item)
{
   _dbContext.Remove(item);
   _dbContext.SaveChanges();
}

Upvotes: 1

Views: 5269

Answers (1)

StakAtak
StakAtak

Reputation: 127

When you do:

var listOld = _parents.Get(model.IdParent).ListChilds;

Entity Framework retrieves information from the context and begin tracking this entities and when you do:

_parents.UpdateChild(item);

you invoke:

_dbContext.Update(item);

Which on other side tries to attaches this entity to a context again and sets its EntityState to Modified and so you get an exception.

When you retrieve information from the database and then work with it, you are in the so called "Connected scenario" and Entity Framework context track all retrieved entities. As soon as you modify some data of this entities, the context sets its EntityState to Modified because of modification is performed. So, then you can call the SaveChanges() method, it builds and executes Update statement in the database.

So you have two options:

  1. Add .AsNoTracking() in your retrieval query so you will do a no tracking queries and no entities will be added to the db context respectively. Then you will work in "Disconnected scenario" and you can use db context Update() method;

  2. Change the UpdateChild method by mapping field by field so changing a field in the entity will set it's EntityState to Modified. Then remove _dbContext.Update(item) at all because SaveChanges() will do the entire work.

Another thing to mention - doing a SaveChanges() in a loop is generally not a good idea. This mean that you send (n) queries to database, where (n) is the number of the records.

Upvotes: 1

Related Questions