I'm having an issue updating an entity with multiple related entities. I've got a very simple model which consists of an article entity and a list of categories the article can be related to. You can choose from a check box list which of these categories are associated to it...which works fine.
The problem crops up when I actually come to update an existing entity using the dbContext. As I am updating this entity, I have already detached it from the context ready to re-attach it later so the update can execute properly.
I can see that after I posting the model, the category(s) are being added to the article entity just fine and it looks like it updates in the repository with no errors occurring.
When I look in the database the article has updated as normal but the category(s) have not.
Here is my (simplified) update code...
public virtual bool Attach(T entity)
{
_dbContext.Entry(entity).State = EntityState.Modified;
_dbSet.Attach(entity);
return this.Commit();
}
Any help will be much appreciated.
Ok I understand what I was doing wrong now. I was using the following to select the entity from the context to model bind before it hits the controller action.
T entity = _dbSet.Find(id);
_dbContext.Entry(T).State = EntityState.Detached;
return entity;
Instead of...
return _dbSet.AsNoTracking().FirstOrDefault(predicate);
By detaching the entity, none of the navigation properties are loaded and therefore no changes can be made against them. Using AsNoTracking() still loads the navigation properties but also allows the entity object to be attached back for an update.
Hope this helps someone else in a similar situation.
Related
I have a controller action where I am attempting to both create a new object in the database, of type FantasyTeam, and also modify an existing object, of type User. However, when the action is called I am receiving a System.InvalidOperationException exception, with the explanation of:
An entity object cannot be referenced by multiple instances of
IEntityChangeTracker.
My code is below.
public ActionResult Create([Bind(Include="ID,FantasyTeamName")] FantasyTeam fantasyteam)
{
if (ModelState.IsValid)
{
var CurrentUser = UserManager.FindById(User.Identity.GetUserId());
fantasyteam.OwnerID = CurrentUser.Id;
CurrentUser.HasTeam = true;
db.FantasyTeams.Add(fantasyteam);
db.Entry(CurrentUser).State = EntityState.Modified;
db.SaveChanges();
return RedirectToAction("Dashboard", "Application");
}
return View(fantasyteam);
}
The Controller Action takes the new FantasyTeam object as a parameter from the View where the team details are selected by user. I then find the currently logged in user and set the OwnerID of the team to match the UserID of the owner, as my Foreign Key. I then go to add the new FantasyTeam to the database and also modify the User record in the database, changing the Boolean HasTeam to true. The adding of the new team works fine on its own... I have been getting the exception once I tried to modify the User object as well.
I have searched on StackOverflow for this exception, and have found many related issues, but I couldn't find a way to apply the other resolutions to my problem. In some cases the issue was that the developers were using two separate data contexts when they should have just been using one, but I don't think that would apply to me here.
Could someone offer insight as to what might be going wrong here?
Thanks!
According to Linq to SQL DataContext Lifetime Management the main causality of the problem comes from the below reason.
Linq to SQL uses a DataContext to manage it's access to the database as well as tracking changes made to entities retrieved from the database. Linq to SQL has a persistent approach to managing its 'connection' to the database via this data context and it basically assumes that you use a single DataContext to make all of your data related access. This doesn't mean that it makes persistent connections to the database, but means that the DataContext instance maintains state about active result sets, which is especially important if change tracking is on which is the default.
The comment posted by haim770 was the answer - I didn't realize that the UserManager object was using a different context than the db object was using. Once I changed the UserManager object to use the same context as db, my code works as intended.
I'm sure I've created my own pain here, but I'm struggling to understand the correct sequence of events to manage creating new entities in my scenario.
In my model I have two objects, ObjectA and ObjectB that both inherit from BaseObject, obviously each with their own additional properties.
In my view, as most of the information is the same, I want the user to be able to just select an option as to which one to create. So they fill out SharedProperty1 and SharedProperty2 (which is a collection navigation property), select an option as to if they want an A or B object, and then fill in a final page which has the last object specific properties.
As I didn't know which entity to create until the user had selected this option, I built a an object in the viewmodel to handle this temporary data. As part of that while they are filling out SharedProperty2 (the collection), as they add new ChildObjects, I create them with entityManager.createEntity('ChildObject'). Then when they reach the end, I create either ObjectA or ObjectB entity and add the child entitites (and other properties) and then try and save.
The problem is it never saves correctly, but I get different results depending on which approach I take. So because the user could just abort the new object process, I was creating the ChildObjects with EntityState.Detached because I thought that would be easier if they got thrown away. I realised though that all the entities created in this way get the id key 0. So then I fixed the keys while I was adding the ChildEntites to the parent (either ObjectA or ObjectB), by assigning them decreasing negative numbers (ie: -1, -2, etc). This resulted in some crazy server-side behaviour with only some entities being saved to the db and complaints of conflicting foreign keys.
This also had a bad smell that I hadn't understood this correctly, and I'd made a mess of it. So now I tried just creating the entities normally (ie: without the Detached flag), and they all get their own unique keys (again breeze appears to follow -1, -2, etc), but now when I try to copy them from my temporary viewmodel collection to the parent object collection, I get the error that an entity with this key is already attached. So now I can't even build up the correct model to save.
I still think I've not understood quite correctly how to handle this, so some pointers would be deeply appreciated.
To head off what I suspect will be a question, why I didn't use RejectChanges to handle the entities being thrown away. Basically a user can add a ChildObject (object gets created by breeze entityManager, added to viewmodel collection, bound to UI), and then decide to just remove it again (currently just gets removed from viewmodel collection) before they save their data. If I used reject changes I would throw away other important entites. I think I'm now going to be a good boy and use the proper detach method if someone removes the ChildObject in the view.
If I understand your question correctly, you are trying to create some properties and then add them to a parent objects collection when saving. Correct me if I am wrong, but Breeze not only supports this, but does so very efficiently. Having come from .NET and C# it was very difficult for me to grasp how easy this can be, but this is what I would do if I were you -
var childA = ko.observable();
var childB = ko.observable();
childA(entityManager.createEntity('ChildObject')); // populate your children
childB(entityManager.createEntity('ChildObject')); // populate your children
Then you can edit them in your view, and when you are ready to save simply add them to the collection.
var save = function() {
isSaving(true);
var parent = ko.observable();
return entityManager.getParent(parent, parentId)
.then(setParents)
.fail(catchError);
function setParents() {
childA().parent(parent());
childB().parent(parent());
entityManager.saveChanges()
.then(complete)
.fail(catchError);
function complete() {
isSaving(false);
return Q.resolve(); // Don't know if you have any unresolved conflicts
}
}
};
Basically in this manner we are -
A : Creating the entities
B : Editing them without performing any changes
C : When we call save we are setting their parent navigation property. In my prior ways (be it right or wrong) I would have simply set ParentId(parentId) and let EF figure out how to navigate but (pardon the pun) this is a breeze with Breeze. We could also just as easily pass in a parent observable and not have to go get it from the manager, it just depends on whether we have it already or not.
Another way you could do this if you want to manage the entities separately is to save a single entity at a time with entityManager.saveChanges([childA]) as they are ready. Just pass in an array with a single entity that you want to save. This may be useful if you are working on multiple entities but they aren't all ready for saving and you need to navigate around your app. Unless you call cancelChanges() Breeze will just keep the entity in cache until you are ready to use it again. In this manner, just make a call for entities in the isAdded() state and you can pull 'em back in and edit again.
I'm using MVC.net and I have 2 classes (case and accident) with a many to many relationship, I'm also using auto mapper to copy View Models to EF and vice versa. Now the problem i've come across is when i do this:
Case theCase = Mapper.Map<CaseEditVM, Case>(theCaseEditVM);
theCase.Accidents.Clear();
UOW.Cases.Update(theCase);
The changes to the case are saved but the link table for accidents is not. Ef totally ignores the Accidents changes.
However when i do:
Case theCase = UOW.Cases.GetByID(someid).Include("Accidents");
theCase.accidents.Clear();
UOW.Cases.Update(theCase);
EF correctly saves the accidents property.
So from what i can tell EF ignores the accident property as its not mapped inside EF yet. Make sense however how do i tell it when mapping the View model i want EF to update the linked properties as well?
The simplest way in your case is first attach case to context and clear changes after you attached it. Otherwise you will have a lot of work. There is no magic which would make this for you. EF doesn't know about changes to relations you did on detached entity and what is even worse once you attach the entity to context you already don't know what records were included in the navigation property so you cannot configure context to reflect that (it must be done per every single related entity) without reloading the whole entity and merging changes between detached and attached one.
I have a 1..* relationship between Review and Recommendations.
The relevant portion of my model (which is also the POCO mapped by EF4):
public class Review
{
public ICollection<Recommendations> Recommendations { get; set; }
}
On an Edit View, i represent the Recommendations as a set of checkboxes.
When i try and add a new Recommendation as part of editing the Review (e.g check another box), nothing is happening - and i know why...
I use the "stub technique" to update my entities - e.g i create a entity with the same key, attach it to the graph, then do ApplyCurrentValues. But this only works for scalar properties, not for navigational properties.
I found this StackOverflow question which looks good, but i am trying to work out how to get this to work with POCO's/Repository (and ASP.NET MVC - detached context).
As i'm using POCO's, review.Recommendations is an ICollection<Recommendation>, so i can't do review.Recommendations.Attach. I'm not using Self-Tracking Entities either, so i need to manually work with the graph/change tracking - which hasn't been a problem until now.
So you can visualize the scenario:
Review:
Recommendations (ICollection<Recommendation>):
RecommendationOne (Recommendation)
RecommendationTwo (Recommendation)
If im on the edit view, two of the checkboxes are already checked. The third one (representing RecommendationThree) is unchecked.
But if i check that box, the above model becomes:
Review:
Recommendations (ICollection<Recommendation>):
RecommendationOne (Recommendation)
RecommendationTwo (Recommendation)
RecommendationThree (Recommendation)
And so i need to attach RecommendationThree to the graph as a new entity.
Do i need hidden fields to compare the posted data the existing entity? Or should i store the entity in TempData and compare that to the posted entity?
EDIT
To avoid confusion, here is the full app stack call:
ReviewController
[HttpPost]
public ActionResult Edit(Review review)
{
_service.Update(review); // UserContentService
_unitOfWork.Commit();
}
UserContentService
public void Update<TPost>(TPost post) where TPost : Post, new()
{
_repository.Update(post); // GenericRepository<Post>
}
GenericRepository - used as GenericRepository<Post>
public void Update<T2>(T2 entity) where T2 : class, new()
{
// create stub entity based on entity key, attach to graph.
// override scalar values
CurrentContext.ApplyCurrentValues(CurrentEntitySet, entity);
}
So, the Update (or Add or Delete) Repository methods needs to be called for each recommendation, depending it's new/modified/deleted.
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.
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.
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?
Entity Framework 4, POCO objects and ASP.Net MVC2. I have a many to many relationship, lets say between BlogPost and Tag entities. This means that in my T4 generated POCO BlogPost class I have:
public virtual ICollection<Tag> Tags {
// getter and setter with the magic FixupCollection
}
private ICollection<Tag> _tags;
I ask for a BlogPost and the related Tags from an instance of the ObjectContext and send it to another layer (View in the MVC application). Later I get back the updated BlogPost with changed properties and changed relationships. For example it had tags "A" "B" and "C", and the new tags are "C" and "D". In my particular example there are no new Tags and the properties of the Tags never change, so the only thing which should be saved is the changed relationships. Now I need to save this in another ObjectContext. (Update: Now I tried to do in the same context instance and also failed.)
The problem: I can't make it save the relationships properly. I tried everything I found:
Controller.UpdateModel and Controller.TryUpdateModel don't work.
Getting the old BlogPost from the context then modifying the collection doesn't work. (with different methods from the next point)
This probably would work, but I hope this is just a workaround, not the solution :(.
Tried Attach/Add/ChangeObjectState functions for BlogPost and/or Tags in every possible combinations. Failed.
This looks like what I need, but it doesn't work (I tried to fix it, but can't for my problem).
Tried ChangeState/Add/Attach/... the relationship objects of the context. Failed.
"Doesn't work" means in most cases that I worked on the given "solution" until it produces no errors and saves at least the properties of BlogPost. What happens with the relationships varies: usually Tags are added again to the Tag table with new PKs and the saved BlogPost references those and not the original ones. Of course the returned Tags have PKs, and before the save/update methods I check the PKs and they are equal to the ones in the database so probably EF thinks that they are new objects and those PKs are the temp ones.
A problem I know about and might make it impossible to find an automated simple solution: When a POCO object's collection is changed, that should happen by the above mentioned virtual collection property, because then the FixupCollection trick will update the reverse references on the other end of the many-to-many relationship. However when a View "returns" an updated BlogPost object, that didn't happen. This means that maybe there is no simple solution to my problem, but that would make me very sad and I would hate the EF4-POCO-MVC triumph :(. Also that would mean that EF can't do this in the MVC environment whichever EF4 object types are used :(. I think the snapshot based change tracking should find out that the changed BlogPost has relationships to Tags with existing PKs.
Btw: I think the same problem happens with one-to-many relations (google and my colleague say so). I will give it a try at home, but even if that works that doesn't help me in my six many-to-many relationships in my app :(.
Let's try it this way:
Attach BlogPost to context. After attaching object to context the state of the object, all related objects and all relations is set to Unchanged.
Use context.ObjectStateManager.ChangeObjectState to set your BlogPost to Modified
Iterate through Tag collection
Use context.ObjectStateManager.ChangeRelationshipState to set state for relation between current Tag and BlogPost.
SaveChanges
Edit:
I guess one of my comments gave you false hope that EF will do the merge for you. I played a lot with this problem and my conclusion says EF will not do this for you. I think you have also found my question on MSDN. In reality there is plenty of such questions on the Internet. The problem is that it is not clearly stated how to deal with this scenario. So lets have a look on the problem:
Problem background
EF needs to track changes on entities so that persistance knows which records have to be updated, inserted or deleted. The problem is that it is ObjectContext responsibility to track changes. ObjectContext is able to track changes only for attached entities. Entities which are created outside the ObjectContext are not tracked at all.
Problem description
Based on above description we can clearly state that EF is more suitable for connected scenarios where entity is always attached to context - typical for WinForm application. Web applications requires disconnected scenario where context is closed after request processing and entity content is passed as HTTP response to the client. Next HTTP request provides modified content of the entity which has to be recreated, attached to new context and persisted. Recreation usually happends outside of the context scope (layered architecture with persistance ignorace).
Solution
So how to deal with such disconnected scenario? When using POCO classes we have 3 ways to deal with change tracking:
Snapshot - requires same context = useless for disconnected scenario
Dynamic tracking proxies - requires same context = useless for disconnected scenario
Manual synchronization.
Manual synchronization on single entity is easy task. You just need to attach entity and call AddObject for inserting, DeleteObject for deleting or set state in ObjectStateManager to Modified for updating. The real pain comes when you have to deal with object graph instead of single entity. This pain is even worse when you have to deal with independent associations (those that don't use Foreign Key property) and many to many relations. In that case you have to manually synchronize each entity in object graph but also each relation in object graph.
Manual synchronization is proposed as solution by MSDN documentation: Attaching and Detaching objects says:
Objects are attached to the object
context in an Unchanged state. If you
need to change the state of an object
or the relationship because you know
that your object was modified in
detached state, use one of the
following methods.
Mentioned methods are ChangeObjectState and ChangeRelationshipState of ObjectStateManager = manual change tracking. Similar proposal is in other MSDN documentation article: Defining and Managing Relationships says:
If you are working with disconnected
objects you must manually manage the
synchronization.
Moreover there is blog post related to EF v1 which criticise exactly this behavior of EF.
Reason for solution
EF has many "helpful" operations and settings like Refresh, Load, ApplyCurrentValues, ApplyOriginalValues, MergeOption etc. But by my investigation all these features work only for single entity and affects only scalar preperties (= not navigation properties and relations). I rather not test this methods with complex types nested in entity.
Other proposed solution
Instead of real Merge functionality EF team provides something called Self Tracking Entities (STE) which don't solve the problem. First of all STE works only if same instance is used for whole processing. In web application it is not the case unless you store instance in view state or session. Due to that I'm very unhappy from using EF and I'm going to check features of NHibernate. First observation says that NHibernate perhaps has such functionality.
Conclusion
I will end up this assumptions with single link to another related question on MSDN forum. Check Zeeshan Hirani's answer. He is author of Entity Framework 4.0 Recipes. If he says that automatic merge of object graphs is not supported, I believe him.
But still there is possibility that I'm completely wrong and some automatic merge functionality exists in EF.
Edit 2:
As you can see this was already added to MS Connect as suggestion in 2007. MS has closed it as something to be done in next version but actually nothing had been done to improve this gap except STE.
I have a solution to the problem that was described above by Ladislav. I have created an extension method for the DbContext which will automatically perform the add/update/delete's based on a diff of the provided graph and persisted graph.
At present using the Entity Framework you will need to perform the updates of the contacts manually, check if each contact is new and add, check if updated and edit, check if removed then delete it from the database. Once you have to do this for a few different aggregates in a large system you start to realize there must be a better, more generic way.
Please take a look and see if it can help http://refactorthis.wordpress.com/2012/12/11/introducing-graphdiff-for-entity-framework-code-first-allowing-automated-updates-of-a-graph-of-detached-entities/
You can go straight to the code here https://github.com/refactorthis/GraphDiff
I know it's late for the OP but since this is a very common issue I posted this in case it serves someone else.
I've been toying around with this issue and I think I got a fairly simple solution,
what I do is:
Save main object (Blogs for example) by setting its state to Modified.
Query the database for the updated object including the collections I need to update.
Query and convert .ToList() the entities I want my collection to include.
Update the main object's collection(s) to the List I got from step 3.
SaveChanges();
In the following example "dataobj" and "_categories" are the parameters received by my controller "dataobj" is my main object, and "_categories" is an IEnumerable containing the IDs of the categories the user selected in the view.
db.Entry(dataobj).State = EntityState.Modified;
db.SaveChanges();
dataobj = db.ServiceTypes.Include(x => x.Categories).Single(x => x.Id == dataobj.Id);
var it = _categories != null ? db.Categories.Where(x => _categories.Contains(x.Id)).ToList() : null;
dataobj.Categories = it;
db.SaveChanges();
It even works for multiple relations
The Entity Framework team is aware that this is a usability issue and plans to address it post-EF6.
From the Entity Framework team:
This is a usability issue that we are aware of and is something we have been thinking about and plan to do more work on post-EF6. I have created this work item to track the issue: http://entityframework.codeplex.com/workitem/864 The work item also contains a link to the user voice item for this--I encourage you to vote for it if you have not done so already.
If this impacts you, vote for the feature at
http://entityframework.codeplex.com/workitem/864
All of the answers were great to explain the problem, but none of them really solved the problem for me.
I found that if I didn't use the relationship in the parent entity but just added and removed the child entities everything worked just fine.
Sorry for the VB but that is what the project I am working in is written in.
The parent entity "Report" has a one to many relationship to "ReportRole" and has the property "ReportRoles". The new roles are passed in by a comma separated string from an Ajax call.
The first line will remove all the child entities, and if I used "report.ReportRoles.Remove(f)" instead of the "db.ReportRoles.Remove(f)" I would get the error.
report.ReportRoles.ToList.ForEach(Function(f) db.ReportRoles.Remove(f))
Dim newRoles = If(String.IsNullOrEmpty(model.RolesString), New String() {}, model.RolesString.Split(","))
newRoles.ToList.ForEach(Function(f) db.ReportRoles.Add(New ReportRole With {.ReportId = report.Id, .AspNetRoleId = f}))