Entity Framework - Saving Changes to Related Objects in Detached State - c#

I'm using the Northwind database as an example for this post where I am having a problem saving detached entities back to the database using the entity framework.
I have the following two methods to get me the territories and the regions:
static List<Region> GetRegions()
{
using (NorthwindEntities entities = new NorthwindEntities())
{
entities.Region.MergeOption = System.Data.Objects.MergeOption.NoTracking;
return entities.Region.ToList();
}
}
static List<Territories> GetTerritories()
{
using (NorthwindEntities entities = new NorthwindEntities())
{
entities.Territories.MergeOption = System.Data.Objects.MergeOption.NoTracking;
return entities.Territories.ToList();
}
}
These methods both work fine and get me the collection of objects I require in the detached state.
I also have a static method called SaveEntity, which takes in both an old entity and the currently editted entity, this is as follows:
static void SaveEntity(EntityObject oldEntity, EntityObject newEntity)
{
using (NorthwindEntities entities = new NorthwindEntities())
{
entities.Attach(oldEntity);
entities.ApplyPropertyChanges(newEntity.EntityKey.EntitySetName, newEntity);
entities.SaveChanges();
}
}
This method partially works where the changes to the object are saved down to the database, but any changes to the relationship of related objects are not saved.
I have the following code calling the above methods as my example:
List<Territories> territories = GetTerritories();
List<Region> regions = GetRegions();
Region region = regions.Where(n => n.RegionID == 2).FirstOrDefault();
Territories oldTerritory = territories.Where(n => n.TerritoryID == "01581").FirstOrDefault();
Territories newTerritory = ObjectCopier.Clone<Territories>(oldTerritory);
newTerritory.TerritoryDescription = "Hello World";
newTerritory.Region = region;
SaveEntity(oldTerritory, newTerritory);
The change to TerritoryDescription is successfully saved, but the change to Region is not, in the database it still remains as RegionID=1 instead of RegionID=2.
Can anyone provide me with some insight to why ApplyPropertyChanges doesn't propogate changes to related objects?
Also, does anyone know of how I can get around this problem?

Instead of fetching regions and territories seperately, fetch both of them in the same query. Something like (I assume you want to update the entity, do not want to create a new one);
static List<Region> GetTerritoriesWithRegions()
{
using (NorthwindEntities entities = new NorthwindEntities())
{
entities.Territories.MergeOption = System.Data.Objects.MergeOption.NoTracking;
return entities.Territories.Include("Region").ToList();
}
}
Then update them as the following;
List<Territories> territoriesWithRegions = GetTerritoriesWithRegions();
Territories territory = territories.Where(n => n.TerritoryID == "01581").FirstOrDefault();
territory.TerritoryDescription = "Hello World";
Region region = territories.Where(p => p.Any(q => q.Region.RegionID == 2)).FirstOrDefault().Region;
territory.Region = region;
SaveEntity(territory);
And save them;
static void SaveEntity(EntityObject entity)
{
using (NorthwindEntities entities = new NorthwindEntities())
{
entities.Attach(entity);
entities.Context.ObjectStateManager.ChangeObjectState(entity, EntityState.Modified);
entities.SaveChanges();
}
}
I have coded this on notepad so there may be mistakes; if any please comment so I will update accordingly.

I think you can find answers here (Alex James is better one).
Entity Framework Updating with Related Entity
Basically, the reason is because in EF relationships are objects too and have statuses like entities (deleted, added,...), so you would need to have the original reference value in the context as well.

Related

Failure to attach a detached entity (entity with the same key is already in the context)

I'm using Entity Framework 6, Code First approach. I'll try to present my problem with a simple piece of code:
public void ViewEntity(MyEntity Entity) // Want to read properties of my entity
{
using (var Db = new MyDbContext())
{
var DummyList = Db.MyEntities.ToList(); // Iteration on this DbSet
Db.MyEntities.Attach(Entity); // Exception
}
}
The exception message is: Attaching an entity of type 'MyProgram.MyEntity' failed because another entity of the same type already has the same primary key value.
From what I've read on MSDN it's an expected behaviour. But what I want on that last line is to first check if there is an entity with the same key already attached to a context; if it is, use it instead, and only otherwise attach my entity to context.
But I've failed to find a way to do so. There are many utility methods on ObjectContext instance (for example GetObjectByKey). I can't test them all 'cause they all ultimately need a qualifiedEntitySetName, and I don't have any in my real imlpementation, because this method should be on an abstract class and it should work for all entity types. Calling Db.Entity(this) is no use, there is no EntityKey which would have EntitySetName.
So all of this became complex really fast. And in my terms I just want to check if the object is already in "cache" (context), use it, otherwise use my object and attach it to this context.
To be clear, I have a detached object from a TreeNode.Tag in the first place, and I just want to use it again, or if it's impossible; if there already is one in the context), use that one instead. Maybe I'm missing some crucial concepts of EF6, I'm just starting out with EF.
I've found a solution for me. As I guessed correctly ObjectContext.GetObjectByKey method does what I need, but first I needed to construct qualifiedEntitySetName, and I found a way to do so. A tad bit cumbersome (using reflection, iterating properties of MyDbContext), but does not compare to a headache of a problem I made out of all this. Just in case, here's the patch of code that is a solution for me:
public SdsAbstractObject GetAttachedToContext()
{
var ObjContext = (SdsDbContext.Current as IObjectContextAdapter).ObjectContext;
var ExistingItem = ObjContext.GetObjectByKey(GetEntityKey()) as SdsAbstractObject;
if (ExistingItem != null)
return ExistingItem;
else
{
DbSet.Attach(this);
return this;
}
}
public EntityKey GetEntityKey()
{
string DbSetName = "";
foreach (var Prop in typeof(SdsDbContext).GetProperties())
{
if (Prop.PropertyType.IsGenericType
&& Prop.PropertyType.GenericTypeArguments[0] == ObjectContext.GetObjectType(GetType()))
DbSetName = Prop.Name;
}
if (String.IsNullOrWhiteSpace(DbSetName))
return null;
else
return new EntityKey("SdsDbContext." + DbSetName, "Id", Id);
}
An Entity can be in one of five stages : Added, Unchanged, Modified, Deleted, Detached.
public void ViewEntity(MyEntity entity) // Want to read properties of my entity
{
using (var Db = new MyDbContext())
{
var DummyList = Db.MyEntities.ToList(); // Iteration on this DbSet
// Set the Modified state of entity or you can write defensive code
// to check it before set the state.
if (Db.Entry(entity).State == EntityState.Modified) {
Db.Entry(entity).State = EntityState.Modified
}
// Attached it
Db.MyEntities.Attach(Entity);
Db.SaveChanges();
}
}
Since EF doesn't know which properties are different from those in the database, it will update them all.

How to update entities which are modified outside the DbContext?

I've a small problem with updating entities if the entity is changed outside the DbContext (is a detached entity). If I attach the modified entity, it's state is not modified.
My code looks like this:
var specificationToSave = GetSpecificationFromTmpStore(userSessionGuid);
using (var context = DataContextFactory.GetDataContext())
{
// this works for update, if I change the values inside the context while debugging
// but it breaks with new entities
context.Specifications.Attach(specificationToSave);
// this works for insert new entities, modified entities will be saved as new entities
context.Specifications.Add((specificationToSave);)
context.SaveChanges();
}
I know NHibernate and it's method SaveOrUpdate. NHibernate decides because of the values if it is updating or inserting the entities.
What is the best practice to do this with EF 4.x and with entities which are modified outside the DbContext?
How can I tell the EF that this entity is in modified state?
If you use the Attach approach on an entity which has already changed, you will also need to tell EF that the entity is modified, after attaching it.
context.Specifications.Attach(entity);
context.Entry(entity).State = EntityState.Modified;
context.SaveChanges();
An alternative is to fetch (with tracking), then update the fields, and save:
var entity = context.Specifications.First(s => s.Id == 1234);
entity.Name = "Foo";
... other changes here
context.SaveChanges();
Another option is to make the changes to the entity after you have reattached it, e.g. as per here
context.Specifications.Attach(entity);
entity.Name = "Foo";
... other changes here
context.SaveChanges();
Edit
You can use generics with DbSet - either class, or method - as follows:
public void Update<TEntity>(TEntity entity)
{
DbContext.Set<TEntity>().Attach(entity);
DbContext.Entry(entity).State = EntityState.Modified;
DbContext.SaveChanges();
}
Edit : For updating of detached Parent / Child Graphs
For updating of simple / shallow parent-child relationships where efficiency and performance is not important, simply deleting all old children and reinserting the new ones is an easy (although ugly) solution.
However, for a more efficient scenario requires us to traverse the graph, detect changes, and then add newly inserted, update existing, ignore unchanged, and delete removed items from the Context.
Slauma shows a great example of this here.
You might want to look at using GraphDiff, which can do all this leg work for you!
For disconnected entities, I found this solution.
For finding changes on an existing entity:
var existing = context.Find<Item>(1);
if (existing != null)
{
context.Entry(existing).CurrentValues.SetValues(changed);
}
Its EntityState will be Modified afterwards but only where there are actual changes.
Full example I did in a unit/integration test:
await using var context1 = new MyContext(new DbContextOptionsBuilder().UseSqlite("Data Source=demo.db").Options);
await context1.Database.EnsureDeletedAsync();
await context1.Database.EnsureCreatedAsync();
await context1.Items.AddAsync(new Item
{
Id = 1,
Name = "Something to start with"
});
await context1.SaveChangesAsync();
await using var context2 = new MyContext(new DbContextOptionsBuilder().UseSqlite("Data Source=demo.db").Options);
var existing = context2.Find<Item>(1);
var entry = context2.Entry(existing);
entry.CurrentValues.SetValues(new Item
{
Id = 1,
Name = "Something to start with"
});
entry.State.Should().Be(EntityState.Unchanged);
entry.CurrentValues.SetValues(new Item
{
Id = 1,
Name = "Updated now."
});
entry.State.Should().Be(EntityState.Modified);
Using Microsoft.EntityFrameworkCore.Sqlite and FluentAssertions.

Why am I getting an InvalidOperationException when trying to re-attach an object

I had a bit of a struggle trying to understand why my code was crashing (which I got to work).
When you look at both the original method and the working one, the placement of one line is different
ctx.Inventories.Attach(this);
I'm puzzled when the original method doesn't work but the second one does.
Can anyone provide some insight?
Here's the exception that I get.
System.InvalidOperationException : An object with the same key already exists in the ObjectStateManager. The ObjectStateManager cannot track multiple objects with the same key.
This is my original method
public void RemoveDependency(int depId)
{
bool returnValue = false;
if (this.Id != 0 && depId > 0)
{
using (ApsEntities ctx = new ApsEntities())
{
var query2 = from d in ctx.Dependencies
where d.Id == depId
select d;
Dependency found = query2.FirstOrDefault();
if (found != null)
{
**ctx.Inventories.Attach(this);**
ctx.ObjectStateManager.ChangeObjectState(this, EntityState.Modified);
this.Dependencies.Remove(found);
ctx.SaveChanges();
}
}
}
return returnValue;
}
Here is my working method
public void RemoveDependency(int depId)
{
bool returnValue = false;
if (this.Id != 0 && depId > 0)
{
using (ApsEntities ctx = new ApsEntities())
{
**ctx.Inventories.Attach(this);**
var query2 = from d in ctx.Dependencies
where d.Id == depId
select d;
Dependency found = query2.FirstOrDefault();
if (found != null)
{
ctx.ObjectStateManager.ChangeObjectState(this, EntityState.Modified);
this.Dependencies.Remove(found);
ctx.SaveChanges();
}
}
}
return returnValue;
}
The behavior has to do with the ObjectStateManager tracking the relationships.
When the current Inventory item is not known to the ObjectContext (as in your first example) then the relationships between Inventory and Dependency is not recognized by the context. Since Inventory is unknown to the ObjectContext your query for a Inventory will load it and the item already exists when you attach it to your ObjectContext.
In the second example you first attach Inventory and then execute the query. The ObjectContext will then explicitly attach the Dependency object to the Inventory item.
The documentation states:
ObjectStateManager tracks query results, and provides logic to merge
multiple overlapping query results. It also performs in-memory change
tracking when a user inserts, deletes, or modifies objects, and
provides the change set for updates. This change set is used by the
change processor to persist modifications.
This behavior can't happen when the related objects are not known to the ObjectContext.

How to save an updated many-to-many collection on detached Entity Framework 4.1 POCO entity

For the last few days I'm trying to properly update my POCO entities. More specific, it's many-to-many relationship collections.
I've three database tables:
Author - 1..n - AuthorBooks - n..1 - Books.
Translates to two POCO entities:
An Author entity with a Books collection and Book entity with a Authors collection.
Case
When I have one active DbContext, retrieve a Book entity, add an Author and call SaveChanges(), the changes are properly send to the database. All fine so far.
However I've a desktop application with limited DbContext lifetime, as displayed in code fragments below.
public Book GetBook(int id)
{
using (var context = new LibariesContext())
{
return context.Books
.Include(b => b.Authors)
.AsNoTracking()
.Single(b => b.BookId == id);
}
}
public Author GetAuthor(int id)
{
using (var context = new LibariesContext())
{
return context.Authors
.AsNoTracking()
.Single(a => a.AuthorId == id);
}
}
A simplified example of various of my business logic methods, wraps it together:
public void BusinessLogicMethods()
{
Book book = GetBook(id: 1);
Author author = GetAuthor(id: 1);
book.Name = "New book title";
book.Authors.Add(author);
SaveBook(book);
}
public void SaveBook(Book book)
{
using (var context = new LibariesContext())
{
context.Entry(book).State = System.Data.EntityState.Modified;
context.SaveChanges();
}
}
Unfortunately the only thing that is really saved here, is the name of the book. The new author was not saved, neither was an Exception thrown.
Questions
What's the best way to save the collection of a detached entity?
Any workaround for this issue?
I'm new to EF 4.1 too, and if I understand your question correctly, I think I ran into this nonsense over the weekend. After trying every approach under the sun to get the entries to update, I found a mantra here on SO (I can't find it any more) and turned it into a generic extension method. So now, after the line:
book.Authors.Add(author);
I would add the line:
context.UpdateManyToMany(book, b => b.Authors)
You might need to restructure your code to make this happen.
Anyway... here's the extension method I wrote. Let me know if it works (no guarantees!!!)
public static void UpdateManyToMany<TSingle, TMany>(
this DbContext ctx,
TSingle localItem,
Func<TSingle, ICollection<TMany>> collectionSelector)
where TSingle : class
where TMany : class
{
DbSet<TSingle> localItemDbSet = ctx.Set(typeof(TSingle)).Cast<TSingle>();
DbSet<TMany> manyItemDbSet = ctx.Set(typeof(TMany)).Cast<TMany>();
ObjectContext objectContext = ((IObjectContextAdapter) ctx).ObjectContext;
ObjectSet<TSingle> tempSet = objectContext.CreateObjectSet<TSingle>();
IEnumerable<string> localItemKeyNames = tempSet.EntitySet.ElementType.KeyMembers.Select(k => k.Name);
var localItemKeysArray = localItemKeyNames.Select(kn => typeof(TSingle).GetProperty(kn).GetValue(localItem, null));
localItemDbSet.Load();
TSingle dbVerOfLocalItem = localItemDbSet.Find(localItemKeysArray.ToArray());
IEnumerable<TMany> localCol = collectionSelector(localItem)?? Enumerable.Empty<TMany>();
ICollection<TMany> dbColl = collectionSelector(dbVerOfLocalItem);
dbColl.Clear();
ObjectSet<TMany> tempSet1 = objectContext.CreateObjectSet<TMany>();
IEnumerable<string> collectionKeyNames = tempSet1.EntitySet.ElementType.KeyMembers.Select(k => k.Name);
var selectedDbCats = localCol
.Select(c => collectionKeyNames.Select(kn => typeof (TMany).GetProperty(kn).GetValue(c, null)).ToArray())
.Select(manyItemDbSet.Find);
foreach (TMany xx in selectedDbCats)
{
dbColl.Add(xx);
}
ctx.Entry(dbVerOfLocalItem).CurrentValues.SetValues(localItem);
}
I came across this question when I was attempting to solve the same problem. In the end I took a different approach which seems to be working. I had to end up exposing a "state" property on the entities and have the calling context set the state of the entities within the object graph.
This reduces the overhead on the web service/data context side to have to determine what's changed, given that the graph could be populated by any number of query permeation. There's also a NuGet package called GraphDiff which might work for you as well(details in the link below).
Anyhow, full details here: http://sanderstechnology.com/2013/solving-the-detached-many-to-many-problem-with-the-entity-framework/12505/

How do I coerce LINQ to SQL to always include a column in UPDATE statements?

I have a trigger for auditing purposes that requires the presence of the audit columns in every update statement.
However, LINQ to SQL will only send the columns in an UPDATE that have changed.
Given that sometimes the same user might edit a column (and thus the audit value for "UpdatedBy" would be the same) LINQ to SQL will encounter trigger errors when attempting updates.
I've dug through Reflector and notice that SubmitChanges in DataContext is using ChangeTracker, but I can't work out if there's any good way to convince LINQ to SQL that the column has changed and so should be included. Worst solution case would be to include all fields in update statements, regardless of changes. However, the ChangeTracker reference is buried within the DataContext and doesn't allow us to inject out own ChangeTracker.
I've toyed with the idea of detaching and reattaching the entity, but this seems convoluted at best. If anyone has an idea I'd appreciate it. Cheers.
Easiest solution:
You'll have to disable concurrency checking (UpdateCheck.Never) on all columns (you can shift click it in the designer, or modify your template if using T4 templates). Alternatively you'll have to copy every field in the example below (could generate a template method to do this).
In any case:
MyDataContext db = new MyDataContext();
MyDataContext db2 = new MyDataContext();
Car betsy = db.Cars.First(c => c.Name == "Betsy");
Car betsy2 = new Car();
betsy2.Id= betsy.Id;
db2.Cars.Attach(betsy2);
/* Could be GetName() or whatever, but allows same */
betsy2.UpdatedBy = betsy.UpdatedBy;
betsy2.OtherField = "TestTestTest";
db2.SubmitChanges();
Some other "solutions" below. Unfortunately all of these involve a double update/whatever, and don't really work if you're doing a delete. As you're auditing is probably backing up to another table somewhere you're going to want to patch in your trigger around updating the last audit entry with '|' when you get your second non '|' or something (if it's always in a transaction perhaps not the end of the world). It's all non-ideal though.
The messy solution:
MyDataContext db = new MyDataContext();
Car car = db.Cars.First(c => c.Id == 1);
car.Name = "Betsy";
car.UpdatedBy = String.Format("{0}|{1}", car.UpdatedBy, DateTime.Ticks);
db.SubmitChanges();
car.UpdatedBy = car.UpdatedBy.Substring(0, car.UpdatedBy.LastIndexOf('|'));
db.SubmitChanges();
The slightly better solution:
public partial class MyDataContext : DataContext
{
public override void SubmitChanges(ConflictMode failureMode)
{
ChangeSet cs = base.GetChangeSet();
foreach (object e in cs.Updates.Union(cs.Inserts))
{
PropertyInfo updatedBy = e.GetType()
.GetProperties()
.FirstOrDefault(p => p.Name == "UpdatedBy");
if (updatedBy == null)
{
base.SubmitChanges(failureMode);
return;
}
string updatedByValue = updatedBy.GetValue(e, null);
string tempValue = String.Format("{0}|{1}", updatedByValue, DateTime.Ticks;
updatedBy.SetValue(e, tempValue);
base.SubmitChanges(failureMode);
updatedBy.SetValue(e, tempValue.Substring(0, tempValue.LastIndexOf('|')));
base.SubmitChanges(failureMode);
}
}
}
The best solution of this type I've found (if you're using the T4 Templates for LINQ to SQL this is even easier):
Either make a partial class file implementing a common interface for each audited entity type or modify the template so that audited entities implement a common interface, e.g.:
public interface IAuditable
{
string UpdatedBy { get; set; }
}
Then modify your SubmitChanges as follows:
public partial class MyDataContext : DataContext
{
public override void SubmitChanges(ConflictMode failureMode)
{
ChangeSet cs = base.GetChangeSet();
foreach (object e in cs.Updates.Union(cs.Inserts))
{
if (typeof(IAuditable).IsAssignableFrom(e))
{
string tempValue = String.Format("{0}|{1}", ((IAuditable)e).UpdatedBy, DateTime.Ticks);
((IAuditable)e).UpdatedBy = tempValue;
base.SubmitChanges(failureMode);
((IAuditable)e).UpdatedBy = tempValue.Substring(0, tempValue.LastIndexOf('|'));
base.SubmitChanges(failureMode);
}
}
}
}

Categories

Resources