entity framework的通用插入或更新
假设我有一个插入方法:
public T Add(T t) { context.Set().Add(t); context.SaveChanges(); return t; }
一般的更新:
public T Update(T updated,int key) { if (updated == null) return null; T existing = _context.Set().Find(key); if (existing != null) { context.Entry(existing).CurrentValues.SetValues(updated); context.SaveChanges(); } return existing; }
我想将它们组合成一个接受任何实体方法的SaveOrUpdate
:
我如何才能最好地实现这一目标,并且有一种更有效的方法可以避免往返数据库,而不是使用context.Set().Find(key)
我有一个方法有点不同:
- 它不会将现有实体设置为
Modified
,而是将其设置为Attached
。 - 它不执行
SaveChanges()
。
我会解释原因,但首先是来源:
public static class DbContextExtensions { public static void AddOrAttach(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 (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; } }
如您所见,在AddOrAttach
方法中,有许多状态我保持不变。
然后有一些逻辑来确定是否应该添加或附加实体。 实质是由上下文跟踪的每个实体都有一个EntityKey
对象。 如果没有,我首先附上它,然后它得到一个。
然后,在某些情况下,实体确实具有EntityKey
,但没有键值。 如果是这样,它将被Added
。 此外,当它有关键值,但它们都是0或更小时,它将被Added
。 (请注意,我假设您使用int
键字段,可能作为复合主键)。
为什么没有SaveChanges?
您的方法逐个存储实体。 但是,通过一次SaveChanges
调用(即在一个事务中)保存多个对象(对象图)更为常见。 如果您希望通过方法执行此操作,则必须将所有调用包装在TransactionScope
(否则启动并提交事务)。 在一个逻辑工作单元中构建或修改您使用的实体然后执行一次SaveChanges
调用会更方便。 这就是我只用这种方法设置实体状态的原因。
为何选择?
人们做了类似的方法来做“upsert”(添加或更新)。 缺点是它将整个实体标记为已修改,而不仅仅是其修改后的属性。 我更喜欢附加一个实体,然后继续使用它发生的任何代码,这可能会修改其中一个或部分属性。
显然,您很清楚将属性设置为已修改的好处,因为您使用了
context.Entry(existing).CurrentValues.SetValues(updated);
这确实是将值复制到现有实体的推荐方法。 每当我使用它时,我都会在我的AddOrAttach
方法之外(以及之后) AddOrAttach
。 但…
是否有一种更有效的方法来避免往返数据库
CurrentValues.SetValues
仅在当前值是数据库值时有效。 所以你不能没有原始实体来使用这种方法。 因此,在断开连接的场景(例如,Web应用程序)中,如果要使用此方法,则无法避免数据库往返。 另一种方法是将实体状态设置为Modified
(具有上述缺点)。 请参阅我的答案,以获得更多关于此的讨论。
您可以使用界面并执行类似的操作。 我使用了一个显式实现,因此您的其余代码不必处理它。
// I am not 100% sold on my chosen name for the interface, if you like this idea change it to something more suitable public interface IIsPersisted { bool IsPersistedEntity{get;} int Key {get;} } public class SomeEntityModel : IIsPersisted{ public int SomeEntityModelId {get;set;} /*some other properties*/ bool IIsPersisted.IsPersistedEntity{get { return this.SomeEntityModelId > 0;}} int IIsPersisted.Key {get{return this.SomeEntityModelId;}} } public T UpdateOrCreate(T updated) where T : class, IIsPersisted { if (updated == null) return null; if(updated.IsPersistedEntity) { T existing = _context.Set ().Find(updated.Key); if (existing != null) { context.Entry(existing).CurrentValues.SetValues(updated); context.SaveChanges(); } return existing; } else { context.Set ().Add(updated); context.SaveChanges(); return updated; } }
编辑
我刚看到这个:
是否有一种更有效的方法,避免往返数据库比使用
context.Set
().Find(key)
如果您希望整个实体从分离状态更新,那么最简单的事情就是这样做。
context.Entry(updated).State = EntityState.Modified; context.SaveChanges();
这会将整个实体标记为脏并将所有内容保存回数据库。