Entity Framework 4.1 Modifying Object and Child Collection - c#

If I have Book object which has a child collection of Comments, Can I update the Book and list of Comments together with entity framework?
I have tried :
_context.Books.Attach(book);
_context.ObjectStateManager.ChangeObjectState(book, EntityState.Modified);
_context.SaveChanges();
with no luck...
getting the following error on the first line:
An object with the same key already exists in the ObjectStateManager. The ObjectStateManager cannot track multiple objects with the same key

More than likely you have a circular dependency (Books has a foreign key reference to Comments, and Comments back to Books). In this case, the UpdateTranslator within EF is unable to determine the dependency order. As far as I can tell, in this model of development, there is no way to pass a hint to EF to indicate what the order is.
The most common way to solve this (that I have seen) is to do a two-phase commit. Make a change to the Book, save it, then make a change to Comments, and save that. I have found that using the Code First approach allows you to be more specific about the relationships and thereby fix many of the problems that I've had.
Edit:
Here's an example:
using (var context = new BookContext())
{
book.Title = "This is the new title";
context.SaveChanges();
book.Comments.Add(new Comment("This is a comment"));
context.SaveChanges();
}
If there is a circular dependency, you could not do the above with a single call to SaveChanges.

Related

How can I attach an entity to the context when one of its children is already attached?

Background
In my application we were running into issues when trying to add a new entity with existing children to the database after mapping it from a DTO using AutoMapper. The entity and its children were not attached to the context, and so the children would be treated as new and EF would attempt to insert duplicates. My solution to this was to automatically attach an entity to the context whenever an object was mapped to a BaseEntity type (BaseEntity is the base class for all of our Model objects, it has an Id property and nothing else). Here is the code:
public TDestination Map<TDestination>(object source) where TDestination : class
{
var result = _mapper.Map<TDestination>(source);
if (typeof(TDestination).IsSubclassOf(typeof(BaseEntity)) && result != null)
_context.Attach(result); //_context is a DbContext
return result;
}
This worked fine in my initial test cases, but now I've run into an issue where the entity I'm attaching has a child that is already attached to the context. This throws "The instance of entity type 'MyChildEntity' cannot be tracked because another instance with the same key value for {'Id'} is already being tracked.".
How can I attach an entity to the context when a child is already attached? I'm trying to keep this method extremely generic so that it can be used by any object that we are trying to map from a DTO to a BaseEntity.
What I've Tried
I've tried grabbing the associated EntityEntry and recursively detach all of its children using the following method before attempting to call Attach():
private void DetachChildren(EntityEntry entity)
{
foreach (var member in entity.Members.Where(x => x.CurrentValue != null))
{
if (IsBaseEntityType(member.CurrentValue.GetType()))
{
var childEntity = _context.Entry(member.CurrentValue);
childEntity.State = EntityState.Detached;
DetachChildren(childEntity);
}
}
}
I've confirmed that this loop does reach the problem child and sets its state to detached, but I still end up getting the same error when calling Attach().
Welcome to the hell that is working with detached entities.
Automapper can be leveraged to update existing entities rather than forming an entity and attaching it. Given an object like an Order:
public void UpdateOrder(OrderDTO orderDTO)
{
var order = _context.Orders.Single(x => x.OrderId = orderDTO.OrderId);
_mapper.Map(orderDTO, order);
_context.SaveChanges();
}
The benefits of this approach is that it handles whether the order happens to be tracked or not, asserts the order exists for something like an Update where it is assumed it does, and when SaveChanges runs, only the fields that actually changed will be updated. If only 1 field changed, the update statement updates that single field. Attaching a new object and setting EntityState to Modified will update all fields. This could introduce unexpected attack vectors to change data you don't expect since a DTO needs to pass enough info to construct a whole entity to avoid unintentionally #null-ing data. The mapping from DTO to entity should ensure that only editable fields are copied across.
In the case where the OrderDTO will contain one or more child collections to update, you will likely need to use a mapping that excludes those collections, then use AfterMap in the mapping configuration to inspect the child collection for new vs. existing vs. removed entities and handle those accordingly. (Add vs. mapper.Map vs. Remove)
Generally the updates can be structured to perform atomic operations that make the entity interactions as straight forward as possible. For instance UpdateOrderDetails(orderDTO) would update information about the order itself, where there would be separate methods such as AddOrderLine(newOrderLineDTO) vs. UpdateOrderLine(orderLineDTO) vs. RemoveOrderLine(orderLineId) etc. rather than having all order line operations and other related changes done through a single UpdateOrder method accepting a whole modified object graph.
The alternative when dealing with graphs and the possibility of tracked entities is that you need to check each and every related entity against the DbSet's .Local or other means to check to see if the entity is tracked. If it is, then you have to replace the references and copy any applicable changes to the already tracked entity. Telling a DbContext to ignore an existing entity isn't always a simple matter as there can be references to that entity in other tracked entities. Generally you'll want to detect a tracked entity reference then update your references to use that tracked reference and update it as needed. It is lots of mucking around with references, and definitely does not work well with Generic methods
Generic operations are tempting from a DNRY standpoint, but dealing with situations where some entities might be tracked vs. not, and then handling type mismatches etc. (source = object = can be anything..) adds a lot of complexity in place of simpler methods to handle operations.

How to transition partial update operation from ObjectContext to DbContext

I am using entity framework 5.0. I am in a rocess od changing my app from ObjectContext to DbContext model. DbContext should be according to microsoft the recommended approach. I use database forst approach and I have generated model form database.
But, at a very first simple task there is a problem. Namely simple update of a record is broken.
Let's have a simple table Item, for ilustration only:
Item
(
ItemId int NOT NULL, -- Primary key
Name nvarchar(50) NOT NULL,
Description NVARCHAR(50)
)
I have noticed that using DbContext does not support updating a record not as ObjectContext does.
In my application I have a simple update method.
public void UpdateItem()
{
MyContext context = new MyContext();
Item item = new Item();
item.ItemId = 666;
context.Items.Attach(item);
// From this point onward EF tracks the changes I make to Item
Item.Description = "Some description";
context.SaveChanges();
}
Using ObjectContext this method correctly updates a record. Using SQL profiler I can see that it generates something like this (greatly simplified!!!)
UPDATE Item
SET Description = 'Some description'
WHERE ItemId = 666
If, however I try to do the same thing in DbContext I get the exception:
System.Exception: Items.aspx.cs - logged from CustomError() ---> System.Data.Entity.Validation.DbEntityValidationException: Validation failed for one or more entities. See 'EntityValidationErrors' property for more details.
at System.Data.Entity.Internal.InternalContext.SaveChanges()
at System.Data.Entity.Internal.LazyInternalContext.SaveChanges()
at System.Data.Entity.DbContext.SaveChanges()
And no database UPDATE is issued to Sql server.
I guess that DbContext validates all the properties and the property Name is null. This by design. I do not intend to modify it, I do not even know what is it and I do not need to know what is it.
Only the property Description was changed. Clearly ObjectContext does not track changes correctly.
How can this problem be resolved?
I have researched the issue and found the something on updating records.
For example this link: https://stackoverflow.com/a/15339512/4601078
db.Users.Attach(updatedUser);
var entry = db.Entry(updatedUser);
entry.Property(e => e.Email).IsModified = true;
// other changed properties
db.SaveChanges();
But this is horrible code. For every property on should add a line like:
entry.Property(e => e.Email).IsModified = true;
This produces ugly unreadable code, an I suspect lamda expression are not stelar in performance.
Even worse are those who propose to make a roundtrip to DB to fetch existing records with all properties populated, update it and the save changes. This is a no go with regard to performance.
So, how to tackle with simple entity updates or is this DbContext just another item in microsofts collection of dead ends which serve no real purpose?
DbContext doesn't really track changes by watching properties, it compares the values to previously known values. And validation always works on the entire entity so the only real choice if you want to do things this way is to disable validation during this operation. See Entity Framework validation with partial updates
If you know for sure that the changes you apply are valid, or you have custom code to validate them, you can turn off validation by EF altogether:
db.Configuration.ValidateOnSaveEnabled = false;
This works OK as long as you do it your way: attach a new entity with a known Id (aka a stub entity) and then modify its properties. EF will only update the properties it detects as having been modified (indeed by comparing original and current values, not, as ObjectContext did, by change notifications). You shouldn't mark the entity itself as modified.
If you don't want to turn off EF's validation, but neither want to mark individual properties as modified, I think this could be a useful alternative (brought to my attention by Alex's answer).

Entity Framework Updating Many to Many relationships properly

In my School EF Model, I have Kids and Tutorials in many-to-many relationship.
Let's assume both Kids and Tutorials have existing items in them, now we just want to change their existing relationships. That is, to add/delete some tutorials from a kid.
var kid; //the request target to modify relationships
//kid.Tutorials has the old existing relationships to be modified by add/del
var tutorialsToAdd; //the request to add relationships
var tutorialsToDel; //the request to del relationships
using (var conn = new SchoolEFModels(efConnectionStr)) {
conn.Kids.Attach(kid);
kid.Tutorials.ForEach(t => conn.Tutorials.Attach(t));
kid.Tutorials.AddRange(tutorialsToAdd); //simple add extension in batch
kid.Tutorials.RemoveRange(tutorialsToDel); //simple del extension in batch
conn.SaveChanges();
}
When I do this, I got an exception saying:
"Cannot insert duplicate key in object 'dbo.Tutorials'. The duplicate key value is (10)."
I can see EF is trying to create new Tutorial items instead of updating the existing relationship for me. Which is what I don't want. You misunderstood me EF!
What is wrong with my code? How do I make it update Many-to-Many relationships?
I figured it out.
Adding/removing it will make EntityState turn to Added/Deleted. Therefore, causing it to reinsert existing Ids as the article mentioned, thank Gert there for the link.
So, if you modify each of the conn.entry(kid/tutorials).State to EntityState.Modified and then call conn.ChangeTracker.DetectChanges(); then conn.SaveChanges(); it will only update the many-to-many table as expected.
UPDATE:
One thing you need to be careful tho. If the in-memory objects list of Kids and Tutorials are linked to each other. e.g. Kids[0].Tutorials[0] == Tutorials[0] && Tutorial[0].Kids[0] == Kids[0] EF will not be able to handle this dead loop for you. You need to break this circular link first.
To do so, my approach is to open a Connection and read the Kid out Includes(Tutorials), and then use the result to update many to many relationship, but not to use the in-memory objects.

Two many-to-one table relationships and insertion with Entity Framework

I have a model similar to this:
Context: The idea is that it's a database of samples. One sample has details, and several samples can be collated together into a CollatedSample, and the details can be collated together as well in a CollatedDetail. So, one CollatedSample has many CollatedDetails, and starts from many Samples, each of which has many Details. A CollatedDetail has many Details too. It's a "nice" square.
My approach to adding records is thus:
var sample = new Sample();
var detail = new Detail();
sample.Details.Add(detail);
// suppose I add a bit more meat to these entities...
var collatedSample = new CollatedSample();
var collatedDetail = new CollatedDetail();
collatedSample.Samples.Add(sample);
collatedSample.CollatedDetails.Add(collatedDetail);
collatedDetail.Details.Add(detail);
context.CollatedSamples.AddObject(collatedSample);
context.SaveChanges();
So I've added all elements to eachother, and added Detail to both Sample and CollatedDetail. On SaveChanges, I get an Update Exception with the jolly message:
Unable to determine the principal end of the 'SamplingModel.FK_Detail_CollatedDetailId' relationship. Multiple added entities may have the same primary key.
What I think might really be happening is there is an attempt to record the Detail entity before the CollatedDetail is recorded. That Detail table, with its two relationships, is the one causing the trouble (not adding them to either Sample nor CollatedDetail confirms it). Perhaps there is a way to specify the order of insertion? I also tried the reverse, to set the parent instead of using .Add() on children collections, with the same result. Otherwise, how do I make this sort of 2-pronged insertion in one shot?
EDIT: tl;dr:
I found a workaround: I removed the Foreign Key between Detail and CollatedDetail like #JaderDiag suggested, and the reference field from the CollatedDetail table. Entity Framework creates partial classes, so it was easy to create other partial classes for both entities and manually join them. This will be much much slower, I suspect, but it provides the same fluidity in later exploitation as the entities would have provided with the foreign key.
This is a workaround, and definitely not the solution I was looking for. Would vote this down if I could.

Caching POCO objects in Entity Framework 4.0

I have a database with a bunch of reference tables like States, Languages, etc.. I would like to be able to cache these tables and then use those types in any ObjectContext I want. So in pseudo code I want to be able to do something like this
var db1 = new PatientObjectContext();
Cache.CacheStates(db1.States.ToList())
var person = new Person { State = Cache.GetState("PA")} ;
var db2 = new PatientObjectContext();
db2.People.Add(person)
db2.SaveChanges();
I saw this blog post (http://blogs.msdn.com/b/alexj/archive/2009/04/22/tip-14-caching-entity-framework-reference-data.aspx). But this didn't apply to me because I think it is not using POCO's. When I try to attach the cached object to the objectContext, I get an exception because an object with that primary key is already in that contexts states collection.
It seems that caching reference tables should be a pretty common problem, but I cant seem to find any straight forward solutions for this.
Alex's post is every bit as relevant to POCO entities as to non-POCOs. Why do you think it isn't?
I figured out what I was doing wrong. Before attaching the cloned cached object to the new context I needed to make sure that it is not already attached. Because attaching the same object 2 times was throwing an exception. So I found some code that lets me check if an Item is already in the context before attaching it.
Is is possible to check if an object is already attached to a data context in Entity Framework?

Categories

Resources