views:

44

answers:

1

I have a entity that I retrieve as follows and is detached from the context:

ctx.Reviews.MergeOption = MergeOption.NoTracking;

Review review = (from r in ctx.Reviews.Include("ReviewNotes")
                 where r.ReviewID == reviewID
                 select r).First();

I then make changes to an object in the relationship:

if (review.ReviewNotes.Count > 0)
{
  ReviewNote r = review.ReviewNotes.ElementAt(0);
  r.Note = "Ugg " + DateTimeOffset.Now.ToString();
  r.CreatedDate = DateTimeOffset.Now;
}

I then attach the Object and loop the children and change it's entity state if needed. When save changes is done nothing is updated.:

ctx.Reviews.Attach(review);
foreach (ReviewNote item in review.ReviewNotes)
{
   if (item.ReviewNoteID == 0)
   {
       ctx.ObjectStateManager.ChangeObjectState(item, EntityState.Added);
   }
   else
   {
       key = ctx.CreateEntityKey("ReviewNotes", item);
       if (ctx.TryGetObjectByKey(key, out original))
       {
           ctx.ApplyCurrentValues<ReviewNote>(key.EntitySetName, item);
       }

   }
 }

ctx.ObjectStateManager.ChangeObjectState(review, EntityState.Modified);
ctx.SaveChanges();
+1  A: 

Because you start with MergeOption.NoTracking your entities are Detached. Then you modify and attached them. You should be aware that Attach results in an Unchanged EntityState — that is, it has not changed since it was attached to the context. So that both the original and current values have the same set of values: the modified ones. That's why it does not get updated once you call SaveChanges method.

I think you also misunderstood the purpose of ApplyCurrentValues method:
It will take the values of the provided detached entity and use its EntityKey to locate the same entity in the context. Then it will replace the attached entity’s current scalar values with the property values from the detached entity.
In your case where you already attached the detached entity, you don't really need to call it, instead you need to Change the state of your ReviewNote entity to Modified so that EF will execute appropiate update methods against your data store:

ctx.Reviews.Attach(review);
foreach (ReviewNote item in review.ReviewNotes) {
   if (item.ReviewNoteID == 0) {
       ctx.ObjectStateManager.ChangeObjectState(item, EntityState.Added);
   }
   else {
       key = ctx.CreateEntityKey("ReviewNotes", item);
       if (ctx.TryGetObjectByKey(key, out original)) {
           // ctx.ApplyCurrentValues(key.EntitySetName, item);
           ctx.ObjectStateManager.ChangeObjectState(item, EntityState.Modified);
       }
   }
 }
ctx.ObjectStateManager.ChangeObjectState(review, EntityState.Modified);
ctx.SaveChanges();


EDIT: Another approach would be to run the same query and get the ReviewNote objects into the memory and then call ApplyCurrentValues on each of them so that only the ones that has a changed property would go into Modified state:

// This time you don't need to attach:
//ctx.Reviews.Attach(review);
// Make sure you have them in the memory:
Review review2 = (from r in ctx.Reviews.Include("ReviewNotes")
                 where r.ReviewID == reviewID
                 select r).First();
foreach (ReviewNote item in review.ReviewNotes) {
   if (item.ReviewNoteID == 0) {
       ctx.ReviewNotes.AddObject(item);
   }
   else {
       key = ctx.CreateEntityKey("ReviewNotes", item);
       if (ctx.TryGetObjectByKey(key, out original)) {
           // Note that the item is a detached object now: 
           ctx.ApplyCurrentValues(key.EntitySetName, item);
       }
   }
 }
ctx.ObjectStateManager.ChangeObjectState(review, EntityState.Modified);
ctx.SaveChanges();

Also note that ApplyCurrentValues does only work with the scalar properties on a single entity and would not take navigation properties into account, otherwise we would just call it one time on Review object without having to go into a loop to apply it on each and every ReviewNote.

Morteza Manavi
Wow it cant be that simple lol I've tried everything but that. I'll give it a try in the morning :)
Big Joe
Ok I tried your answer and it doesnt work sort of. Actually the row was updated to the db but so were the others that didnt have changes made to them. I have 5 review notes exisiting and all 5 were updated not just the actual modified row. Do you have any othe idea's you can share?
Big Joe
Yes, that's expectable. Since we just changed every review's state to Modified regardless of whether or not they have actually changed. I posted a second code, this time we do not attach the modified objects, instead, have them apply their modified values to the in memory attached related objects.
Morteza Manavi
Thanks for the response. In your second answer it doesn't work in the add of the new ReviewNote. Throw exception "The ObjectStateManager does not contain an ObjectStateEntry with a reference to an object". It seems to do that when the Review entity is not attached. What is the best way to update detached entities fundamentally speaking?
Big Joe
Sorry, I forgot to change the Add new object part. As you can see in the updated code above, now that we have a detached new object and want to add it to the DB, we simply call the ObjectSet.AddObject(entity) so the entity will get an automatically generated temporary EntityKey and its EntityState will be set to Added.
Morteza Manavi
Well, generally, there is no one best solution fits all scenarios exists, it really depends on the architecture. For example, if the reason of your entity being detached is that you are shipping them to other layers with WCF then Self Tracking Entities are the best solution to that where by using them you would not need to be worry about who needs Add and who needs update.
Morteza Manavi
Thanks for all your help. I've punted on trying to manage updates to detached entities. Very frustrating that MS would make it so difficult to do. Doing a pre-retrieve makes sense on why it must be done since the entity has to tracking, but there just seems to have to be a better way. I'll at some point look into self tracking entities. Thank again Morteza for all your help. At least I have a better understanding of whats going on.
Big Joe
You are very welcome :) and I agree that for simple tasks, it's very easy to work with EF, but once you want to tailor it for your specific scenario, things gets a little complicated and you need to have a very good understanding of the EF API to handle that.
Morteza Manavi