Reputation: 1316
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
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:
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;
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