I have a 1..* relationship between Review and Recommendations.
The relevant portion of my model (which is also the POCO mapped by E
I've accepted @jfar's answer because he put me on the right track, but thought i'd add an answer here for other people's benefit.
The reason the relationships were not getting updated is for the following reasons:
1) Completely disconnected scenario. ASP.NET = stateless, new context newed up each HTTP request.
2) Edited entity created by MVC (model binding), but not existing in graph.
3) When using POCO's with no change tracking, performing .Attach
on an entity will add it to the graph, but the entity and any child relationships will be Unchanged.
4) I use the stub entity trick and ApplyCurrentValues
to update the entity, but this only works for scalar properties, not navigational ones.
So - in order to get the above to work, i would have to explicity set the EntityState
for the object (which happens automatically because of ApplyCurrentValues
), and also the navigational properties.
And there is the problem - how do i know if the navigational property was added/modified/deleted? I have no object to compare to - only a entity which i know was "edited", but i don't know what was edited.
So the solution in the end was this:
[HttpPost]
public ActionResult Edit(Review review)
{
var existingReview = _service.FindById(review.Id); // review is now in graph.
TryUpdateModel(existingReview); // MVC equivalent of "ApplyCurrentValues" - but works for ALL properties - including navigationals
_unitOfWork.Commit(); // save changed
}
That's it. I don't even need my _service.Update
method - as i don't need the stub trick anymore - because the review is in the graph with the retrieval, and ApplyCurrentValues
is replaced by TryUpdateModel
.
Now of course - this is not a concurrency-proof solution.
If i load the Review Edit View, and before i click "Submit" someone else changes the Review, my changes could be lost.
Fortunately i have a "last-in-wins" concurrency mode, so it's not an issue for me.
I love POCO's, but man are they a pain when you have the combination of a stateless environment (MVC) and no change tracking.
Perhaps I need more context but whats wrong with:
recommendations.Add(newRecomendation)
?
In reply to comment:
Ok so whats wrong with
SomeServiceOrRepository.AddNewRecommendation( newRecommendation )
or
SomeServiceOrRepository.AddNewRecommendation( int parentId, newRecommendation )
Last Sentence? You mean the two questions?
This shouldn't be hard at all.
To summarize my answer I think you are doing things "the hard way" and really should focus on posting form values that correspond to the CRUD action your trying to accomplish.
If a new entity could come in at the same time as your edited entities you should really prefix them differently so the model binder can pick up on it. Even if you have multiple new items you can use the same [0] syntax just prefix the "name" field with New or something.
A lot of times in this scenario you can't rely on Entity Frameworks graph features because removing an entity from a collection never means it should be set for deletion.
If the form is immutable you could also try using the generized attach function off of ObjectSet:
theContect.ObjectSet<Review>().Attach( review )
Tons of ways out of this. Maybe you could post your controller and view code?
Working with detached object graphs is my favorite drawback of EF. Simply pain in the ass. First you have to deal with it at your own. EF will not help you with it. It means that in addition to Review
you also have to send some information about made changes. When you attach Review
to context it sets Review
all Recommendation
and all relations to Unchanged
state. ApplyCurrentValues
works only for scalar values as you have already found. So you have to use your additional information about made changes and set state of relations to Added
by using ObjectContext.ObjectStateManager.ChangeRelationshipState
.
I personaly gave up with this approach and I'm loading object graph from DB first merging my changes into attached graph and save it.
I answered similar question more deeply here.