How to update entities which are modified outside the DbContext?
Asked Answered
W

2

16

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?

Wainscoting answered 6/9, 2012 at 9:14 Comment(0)
A
29

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!

Amora answered 6/9, 2012 at 9:17 Comment(5)
Many thanks :-) That's what I was looking for. The second way is the best one, but in this case not possible. So I have to update the state of the DbEntityEntry directly. Not a very nice solution but it works.Wainscoting
Is it true, if a have to set the state of every related entity of the 'specificationToSave'. for example if I have a list of modified items in my 'specificationToSave'. I really hope there is a simple solution to do that...Wainscoting
@Jürgen parent : child scenarios need special consideration, since you may need to add, delete or update children. FWIW I typically just do a hash on the children to see if any have changed, and if they have, then to delete all existing and insert all children again. But thats just me being lazy.Amora
thanks for the generic solution. But is is possible to attach and modify the state of an entity and all related entities in one way? My 'specificationToSave' has some lists of other entities which are also modified. Do I really have to iterate through all the sub entities to set the state to modified?Wainscoting
You don't have to tell EF that the state is modifed and attach it, When you do context.Entry(entity).State = EntityState.Modified;, you are not only attaching the entity to the DbContext, you are also marking the whole entity as dirty, futher is explained here: #30988306 @AmoraHemp
C
0

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.

Carnallite answered 12/1, 2021 at 15:58 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.