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(); 

这会将整个实体标记为脏并将所有内容保存回数据库。