I have a method that acts a little bit differently:
- It doesn't set an existing entity as
Modified
, but as Attached
.
- It doesn't execute
SaveChanges()
.
I'll explain why, but first the source:
public static class DbContextExtensions
{
public static void AddOrAttach<T>(this DbContext context, T entity)
where T : class
{
#region leave conditions
if (entity == null) return;
var entry = context.Entry(entity);
var leaveStates = new[]
{
EntityState.Deleted,
EntityState.Modified,
EntityState.Unchanged
};
if (leaveStates.Contains(entry.State)) return;
#endregion
var entityKey = context.GetEntityKey(entity);
if (entityKey == null)
{
entry.State = EntityState.Unchanged;
entityKey = context.GetEntityKey(entity);
}
if (entityKey.EntityKeyValues == null
|| entityKey.EntityKeyValues.Select(ekv => (int)ekv.Value).All(v => v <= 0))
{
entry.State = EntityState.Added;
}
}
public static EntityKey GetEntityKey<T>(this DbContext context, T entity)
where T : class
{
var oc = ((IObjectContextAdapter)context).ObjectContext;
ObjectStateEntry ose;
if (null != entity && oc.ObjectStateManager
.TryGetObjectStateEntry(entity, out ose))
{
return ose.EntityKey;
}
return null;
}
}
As you see, in the AddOrAttach
method there are a number of states that I leave unaltered.
Then there is some logic to determine whether the entity should be added or attached. The essence is that every entity that's tracked by the context has an EntityKey
object. If it hasn't, I attach it first so it gets one.
Then, there are scenarios in which an entity does have an EntityKey
, but without key values. If so, it will be Added
. Also when it's got key values, but they're all 0 or smaller, it will be Added
. (Note that I assume that you use int
key fields, possibly as composite primary keys).
Why no SaveChanges?
Your methods store entities one-by-one. However, it's far more common to save multiple objects (object graphs) by one SaveChanges
call, i.e. in one transaction. If you'd want to do that by your methods, you'd have to wrap all calls in a TransactionScope
(or start and commit a transaction otherwise). It's far more convenient to build or modify entities you work with in one logical unit of work and then do one SaveChanges
call. That's why I only set entity state by this method.
Why Attach?
People made similar methods that do an "upsert" (add or update). The drawback is that it marks a whole entity as modified, not just its modified properties. I prefer to attach an entity and then continue the code with whatever happens to it, which may modify one or some of its properties.
Evidently, you are well aware of the benefit of setting properties as modified, because you use
context.Entry(existing).CurrentValues.SetValues(updated);
This is indeed the recommended way to copy values into an existing entity. Whenever I use it, I do it outside (and following) my AddOrAttach
method.
But...
is there a more efficient way that avoids a round trip to the database
CurrentValues.SetValues
only works if the current values are the database values. So you can't do without the original entity to use this method. So, in disconnected scenarios (say, web applications), if you want to use this method, you can't avoid a database roundtrip. An alternative is to set the entity state to Modified
(with the drawbacks mentioned above). See my answer here for some more discussion on this.