Is it really impossible to update a child collection in EF out of the box (aka not bad)?

Say you have these classes in your entities.

public class Parent { public int ParentID { get; set; } public virtual ICollection<Child> Children { get; set; } } public class Child { public int ChildID { get; set; } public int ParentID { get; set; } public virtual Parent Parent { get; set; } } 

And you have a user interface for updating Parent along with its Children , that is, if the user adds a new Child , then you need to insert, if the user is editing an existing Child , then you need an update, and if the user removes Child , then you need to delete. Now, obviously, if you use the following code

 public void Update(Parent obj) { _parent.Attach(obj); _dbContext.Entry(obj).State = EntityState.Modified; _dbContext.SaveChanges(); } 

he will not be able to detect changes inside the Child , because EF cannot detect changes inside the navigation property.

I asked this question 4 times and got mixed answers. So is it even possible to do this without complication? This problem can fix the problem by dividing the user interface between Parent and Child , but I don’t want to, because merging both Child and Parent in one menu is quite common in developing business applications and is more user-friendly.

UPDATE: I am trying to find a solution below, but it does not work.

 public ActionResult(ParentViewModel model) { var parentFromDB = context.Parent.Get(model.ParentID); if (parentFromDB != null) { parentFromDB.Childs = model.Childs; } context.SaveChanges(); } 

Instead of detecting changes within children, EF will not be able to tell what to do with the old child. For example, if parentFromDB has 3 children, then the first time I pull it out of the database, I delete the 2nd and 3rd children. Then I get The relationship could not be changed because one or more of the foreign-key properties is non-nullable when saved.

I believe this happened: Relations cannot be changed because one or more properties of the foreign key cannot be reset

Which took me back to the square, because in my script I can’t just extract from the database and update the record and call SaveChanges .

+7
c # entity-framework entity-framework-6
source share
2 answers

because EF cannot detect changes inside the navigation property

This seems to be a slightly distorted description of the fact that _dbContext.Entry(obj).State = EntityState.Modified does not mark the properties of the navigator as changed.

Of course, EF tracks changes in navigation properties. It tracks changes in the properties and associations of all objects that are context bound. Therefore, the answer to your question is now positively stated ...

Is it possible to update a child collection in EF from a window

...: yes .

One thing: you are not doing this out of the box .

The “out of the box” method for updating any object, whether it be a parent or a child in a collection, is:

  • Get entities from the database.
  • Change their properties or add / remove items to your collections
  • Call SaveChanges() .

It's all. Ef tracks changes and you never set the State object explicitly.

However, in a disabled (n-tier) scenario, this becomes more complex. We serialize and deserialize objects, so there can be no context that tracks their changes. If we want to store objects in the database, now our task is to make the changes known to EF. There are two ways to do this:

  • Set states manually based on what we know about entities (for example: primary key> 0 means that they exist and must be updated)
  • Draw state: retrieve entities from the database and reapply the changes to the deserialized objects.

When it comes to associations, we always need to draw state. We need to get the current entities from the database and determine which children were added / removed. There is no way to do this from the most deserialized graphical object.

There are various ways to alleviate this boring and complex state mapping task, but this is beyond the scope of this Q & A. Some links:

  • Shared repository to update the entire aggregate
  • Graphdiff
+7
source share

Your oddity unites him.

This requires Lazy loading to get the children (obviously for your use)

// get parent

 var parent = context.Parent.Where(x => x.Id == parentId).SingleOrDefault(); 

I wrote a whole test method for you. (apply to your case)

EmailMessage (parent) is parent and it does not have one or more email messages (child's)

  [TestMethod] public void TestMethodParentChild() { using (var context = new MyContext()) { //put some data in the Db which is linked //--------------------------------- var emailMessage = new EmailMessage { FromEmailAddress = "sss", Message = "test", Content = "hiehdue", ReceivedDateTime = DateTime.Now, CreateOn = DateTime.Now }; var emailAttachment = new EmailAttachment { EmailMessageId = 123, OrginalFileName = "samefilename", ContentLength = 3, File = new byte[123] }; emailMessage.EmailAttachments.Add(emailAttachment); context.EmailMessages.Add(emailMessage); context.SaveChanges(); //--------------------------------- var firstEmail = context.EmailMessages.FirstOrDefault(x => x.Content == "hiehdue"); if (firstEmail != null) { //change the parent if you want //foreach child change if you want foreach (var item in firstEmail.EmailAttachments) { item.OrginalFileName = "I am the shit"; } } context.SaveChanges(); } } 

Refresh

Make your AutoMappper stuff ... as you said in your comment.

Then, when you are ready to save, and you return it as the correct types, that is, once that represents the entities (Db), do it.

 var modelParent= "Some auto mapper magic to get back to Db types." var parent = context.Parent.FirstOrDefault(x => x.Id == modelParent.Id); //use automapper here to update the parent again if (parent != null) { parent.Childs = modelParent.Childs; } //this will update all childs ie if its not in the new list from the return //it will automatically be deleted, if its new it will be added and if it // exists it will be updated. context.SaveChanges(); 
+1
source share

All Articles