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.开发者_Go百科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();
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.
精彩评论