Object structure, problems with updating related objects

I am currently working on a project using the latest version of the Entity Framework, and I ran into a problem that I seem to be unable to solve.

When it comes to updating existing objects, I can pretty easily update the properties of the ok object until I get to a property that is a reference to another class.

In the example below, I have a class Foo that stores various properties, and 2 of them are instances of other classes

public class Foo { public int Id {get; set;} public string Name {get; set;} public SubFoo SubFoo {get; set} public AnotherSubFoo AnotherSubFoo {get; set} } 

When I use the Edit() method below, I go through the object I would like to update, and I can manage to get the Name to update correctly, however I could not find a way in which to get the SubFoo property into change. For example, if the SubFoo class has a Name property, and it has been changed and differs from my DB and newFoo , it is not updated.

 public Foo Edit(Foo newFoo) { var dbFoo = context.Foo .Include(x => x.SubFoo) .Include(x => x.AnotherSubFoo) .Single(c => c.Id == newFoo.Id); var entry = context.Entry<Foo>(dbFoo); entry.OriginalValues.SetValues(dbFoo); entry.CurrentValues.SetValues(newFoo); context.SaveChanges(); return newFoo; } 

Any help or pointers would be greatly appreciated.

UPDATE: Based on Slauma's comment, I changed my method to

 public Foo Edit(Foo newFoo) { var dbFoo = context.Foo .Include(x => x.SubFoo) .Include(x => x.AnotherSubFoo) .Single(c => c.Id == newFoo.Id); context.Entry(dbFoo).CurrentValues.SetValues(newFoo); context.Entry(dbFoo.SubFoo).CurrentValues.SetValues(newFoo.SubFoo); context.SaveChanges(); return newFoo; } 

When you run this now, I get an error message:

The object type Collection'1 is not part of the model for the current context.

To try to get around this, I added code to try to subclass newFoo in the context, but this is with an error saying that the ObjectManager already had an entity:

An object with the same key already exists in the ObjectStateManager. ObjectStateManager cannot track multiple objects with the same key

+53
c # entity-framework ef-code-first
Nov 05 '12 at 16:22
source share
3 answers

CurrentValues.SetValues only updates scalar properties, but has no related objects, so you should do the same for each related object:

 public Foo Edit(Foo newFoo) { var dbFoo = context.Foo .Include(x => x.SubFoo) .Include(x => x.AnotherSubFoo) .Single(c => c.Id == newFoo.Id); context.Entry(dbFoo).CurrentValues.SetValues(newFoo); context.Entry(dbFoo.SubFoo).CurrentValues.SetValues(newFoo.SubFoo); context.Entry(dbFoo.AnotherSubFoo).CurrentValues.SetValues(newFoo.AnotherSubFoo); context.SaveChanges(); return newFoo; } 

If the connection could be completely deleted or was created, you also need to explicitly handle these cases:

 public Foo Edit(Foo newFoo) { var dbFoo = context.Foo .Include(x => x.SubFoo) .Include(x => x.AnotherSubFoo) .Single(c => c.Id == newFoo.Id); context.Entry(dbFoo).CurrentValues.SetValues(newFoo); if (dbFoo.SubFoo != null) { if (newFoo.SubFoo != null) { if (dbFoo.SubFoo.Id == newFoo.SubFoo.Id) // no relationship change, only scalar prop. context.Entry(dbFoo.SubFoo).CurrentValues.SetValues(newFoo.SubFoo); else { // Relationship change // Attach assumes that newFoo.SubFoo is an existing entity context.SubFoos.Attach(newFoo.SubFoo); dbFoo.SubFoo = newFoo.SubFoo; } } else // relationship has been removed dbFoo.SubFoo = null; } else { if (newFoo.SubFoo != null) // relationship has been added { // Attach assumes that newFoo.SubFoo is an existing entity context.SubFoos.Attach(newFoo.SubFoo); dbFoo.SubFoo = newFoo.SubFoo; } // else -> old and new SubFoo is null -> nothing to do } // the same logic for AnotherSubFoo ... context.SaveChanges(); return newFoo; } 

Ultimately, you also need to set the state of the attached objects to Modified if the relation has been changed and scalar properties as well.

Edit

If - according to your comment - Foo.SubFoo is actually a compilation, not just a link, you will need something similar to update related objects:

 public Foo Edit(Foo newFoo) { var dbFoo = context.Foo .Include(x => x.SubFoo) .Include(x => x.AnotherSubFoo) .Single(c => c.Id == newFoo.Id); // Update foo (works only for scalar properties) context.Entry(dbFoo).CurrentValues.SetValues(newFoo); // Delete subFoos from database that are not in the newFoo.SubFoo collection foreach (var dbSubFoo in dbFoo.SubFoo.ToList()) if (!newFoo.SubFoo.Any(s => s.Id == dbSubFoo.Id)) context.SubFoos.Remove(dbSubFoo); foreach (var newSubFoo in newFoo.SubFoo) { var dbSubFoo = dbFoo.SubFoo.SingleOrDefault(s => s.Id == newSubFoo.Id); if (dbSubFoo != null) // Update subFoos that are in the newFoo.SubFoo collection context.Entry(dbSubFoo).CurrentValues.SetValues(newSubFoo); else // Insert subFoos into the database that are not // in the dbFoo.subFoo collection dbFoo.SubFoo.Add(newSubFoo); } // and the same for AnotherSubFoo... db.SaveChanges(); return newFoo; } 
+86
Nov 05 '12 at 17:29
source share

Just thought I'd post the link below, as it really helped me understand how to update related objects.

Updating entity infrastructure related data in an asp net mvc application

NOTE. I slightly changed the logic that is shown in the UpdateInstructorCourses function according to my needs.

+3
Aug 15 '13 at 2:38
source share

You can also call tables yourself

 MyContext db = new MyContext // I like using asynchronous calls in my API methods var OldFoo = await db.Foo.FindAsync(id); var OldAssociateFoo = db.AssociatedFoo; var NewFoo = OldFoo; var NewAssociatedFoo = OldAssociatedFoo; NewFoo.SomeValue = "The Value"; NewAssociatedFoo.OtherValue = 20; db.Entry(OldFoo).CurrentValues.SetValues(NewFoo); db.Entry(OldAssociatedFoo).CurrentValues.SetValues(NewAssociatedFoo); await db.SaveChangesAsync(); 
0
Feb 23 '15 at 4:17
source share



All Articles