0

It's not the first time I run into this entity framework struggle of updating 2 objects that have a list of children with the same child objects inside that list.

A typical structure for this example would be a parent object with a list of child objects that have a tag list each. i.e:

class Parent{ 
public virtual ICollection<Child> Childs{get ;set;}
}

class Child {
public virtual IColletion<Tag> Tags{get; set;}
}
class Tag{
public int Id {get; set;}
public string Text {get; set;}
}

The problem being when trying to update the parent object and its children on the same run, I cannot seem to be able to find a satisfying solution to say to entity framework that the entity Tag can be duplicated.

I most of the time follow this struture to update parents with childs in entity framework. How to add/update child entities when updating a parent entity in EF

To solve this issue I used to hard code the join table Tag_To_Childs and only work with the ids of the tag object, this way entity framework doesn't run in the duplicated key issue. Anyway i would be glad to know if anybody knows a more elegant solution to this problem.

Here is how I actually update my child objects and the tag Lists

   public async Task<List<Child>> updateChilds(Parent entry, Parent existingParent)
// entry being the new version and existingParent the tracked Db version
            {
                if (existingParent != null)
                {

                    // Delete children
                    foreach (var existingChild in existingParent.Childs.ToList())
                    {
                        if (!entry.Charts.Any(c => c.ChildId== existingChild.ChildId))
                            _context.Childs.Remove(existingChild);
                    }
                    // Update and Insert children
                    foreach (var childentry in entry.Childs)
                    {
                        var existingChild = existingParent.Childs
                            .Where(c => c.ChildId== childentry.ChildId)
                            .SingleOrDefault();

                        if (existingChild != null)
                        // Update child
                        {
                            await updateChildTags(existingChild, childentry);
                            _context.Entry(existingChild).CurrentValues.SetValues(childentry);
                        }
                        else
                        {
                            // Insert child
                            var newChild = new Chart
                            {

                                Title = childentry.Title,
                                Order = childentry.Order
                            };
                            newChild = await updateChildTags(newChild, childentry);
                            existingParent.Childs.Add(newChild);
                        }
                    }
                    await _context.SaveChangesAsync();
                }
                return existingParent.Childs.ToList();
            }


            public async Task<Child> updateChartTypeQuestions(Child realBlock, Child entry)
            {

                //delete bad tags
                List<Tag> toRemove = new List<Tag>();
                foreach (var existingChild in realBlock.Tags.ToList())
                {
                    if (!entry.Tags.Any(c => c.TagId== existingChild.TagId))
                        toRemove.Add(existingChild);
                }
                //add new ones
                foreach (var toAdd in entry.Tags.ToList())
                {
                    if (!realBlock.Tags.Any(c => c.TagId== toAdd.TagId))
                    {
                        Tag tag = await _context.Tags.FirstOrDefaultAsync(c => c.TagId == toAdd.TagId);
                        realBlock.Tags.Add(tag);
                    }
                }

                foreach (var toR in toRemove)
                {
                    realBlock.Tags.Remove(toR);
                }
                //the save change is called by parent function
                return realBlock;
            }

The problem with this code, is if 2 childs have the same tag, only the second one will be updated correctly and the tag will be removed from the first child object.

Priyank
  • 72
  • 5
Antoine Dussarps
  • 453
  • 5
  • 15
  • I'm not sure if we can understand what the issue is - could you elaborate on what is happening vs what you want it to do? also could you provide the code of where you are actually interacting with these classes? – Robert Petz Jun 12 '17 at 15:36
  • I updated the question with a more precise code. I also have a version where i use 2 different contexts but it doesn't do the trick neither. – Antoine Dussarps Jun 12 '17 at 15:53
  • This is likely your issue - your code is getting the tags to remove then adding back in the tags it wants before removing them. It's probably an ordering issue - always perform removes before adds if you intend to keep some of the removed tags. – Robert Petz Jun 12 '17 at 16:08
  • also (as an unrelated note) you perform a null check against `existingParent`, but if existing parent IS null then the next line where you try to access a property on `existingParent` it *will* throw a null reference exception – Robert Petz Jun 12 '17 at 16:08
  • I will try this asap to check, but the problem is more likely related to the fact the 2 child object share the same tag, and entity can only track one tag entity at the same time, it works great if the tags on the 2 childs are different or if there is only one child to update – Antoine Dussarps Jun 12 '17 at 18:08
  • that is simply not true, the entity framework can absolutely track an entity that is related to multiple other entities at the same time - so long as the database structure allows that record to relate to multiple different tables. you have to consider what it is doing when it actually applies the relationship - if the tag has only one column that relates to another table then yes it will get overwritten by whatever does the update last, but if the tag either does not have a relational column or it has a column for each related table then yes EF should have no problem tracking that – Robert Petz Jun 12 '17 at 19:14
  • Yes, it's the comportment I would expect, the tag actually doesnt not have any column that link him to the child object, but every time I try to update 2 childs with the same tag, I either end up with one child with no tag, or I run in a duplicated tracking key error. Any idea how to solve this? – Antoine Dussarps Jun 13 '17 at 08:38
  • Hmmmm - it's very hard to follow along with the 'Parent' 'Child' 'Tag' schema you have here without being able to quickly reference through it in an IDE, but it is sounding like you are trying to tell EF you want to insert the same entity twice (likely on the `realBlock.Tags.Add(tag)` line.) whenever you call `Add` it is going to try to insert the entity into the database on save, so instead of doing that try persisting the tag in question once and then querying it out when needed or passing the attached entity along whenever you want to reference it into another entity – Robert Petz Jun 13 '17 at 08:53
  • 1
    I was finally able to solve this. I looked into the Db and saw entitry framework core default this relationship to a one to many and stored the foreign key on the tag table. Being so one tag coulb be associated to only one child. I needed to set explicitly the join table Child_Tag and defined the relationship on my model builder. I would have hoped that entity would have implictly set a join table but apparently in core you need to set it explicitly. Thanks for your answers anyway! – Antoine Dussarps Jun 13 '17 at 09:33

0 Answers0