实体框架的通用插入或更新 [英] Generic Insert or Update for Entity Framework

查看:70
本文介绍了实体框架的通用插入或更新的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

说我有一个插入方法:

public T Add<T>(T t)
{
   context.Set<T>().Add(t);
   context.SaveChanges();
   return t;
}

和一般更新:

public T Update<T>(T updated,int key)
{
    if (updated == null)
        return null;

    T existing = _context.Set<T>().Find(key);

    if (existing != null)
    {
        context.Entry(existing).CurrentValues.SetValues(updated);
        context.SaveChanges();
    }

    return existing;
}

我想将它们合并为一个 SaveOrUpdate 接受任何实体方法:

I'd like to combine them into one SaveOrUpdate that accepts any entity method:

我如何最好地实现这一点,并且有比该方法更有效的方法来避免往返数据库使用 context.Set< T>(Find(key)

How would I best achieve this and is there a more efficient way that avoids a round trip to the database than using context.Set<T>().Find(key)

推荐答案

我有一个行为稍有不同的方法:

I have a method that acts a little bit differently:


  • 它没有将现有实体设置为已修改,但附加为

  • 它不执行 SaveChanges()

  • 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;
    }
}

如您所见,在 AddOrAttach 方法中有许多状态我保持不变。

As you see, in the AddOrAttach method there are a number of states that I leave unaltered.

然后,有一些逻辑来确定是否应添加或附加实体。本质上是,上下文跟踪的每个实体都有一个 EntityKey 对象。

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.

然后,在某些情况下,实体确实具有 EntityKey ,但没有键值。如果是这样,它将被添加。同样,当它具有键值,但都等于或小于0时​​,将被添加。 (请注意,我假设您使用 int 键字段,可能将其用作复合主键。)

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).

您的方法将实体一一存储。但是,更常见的是通过一个 SaveChanges 调用来保存多个对象(对象图),即在一个事务中。如果您想通过自己的方法来做到这一点,则必须将所有调用包装在 TransactionScope 中(否则,请启动并提交交易)。在一个逻辑工作单元中构建或修改要使用的实体,然后进行一次 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.

人们做了类似的方法来执行 upsert操作。 (添加或更新)。缺点是它将整个实体标记为已修改,而不仅仅是其已修改的属性。我更喜欢附加一个实体,然后继续执行代码,以处理发生的任何事情,这可能会修改它们的一个或某些属性。

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

这确实是将值复制到现有实体中的推荐方法。每当使用它时,我都会在 AddOrAttach 方法之外(和之后)进行操作。
但是...

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 仅在当前值为数据库值时才起作用。因此,没有原始实体就无法使用此方法。因此,在断开连接的场景(例如Web应用程序)中,如果要使用此方法,就无法避免数据库往返。一种替代方法是将实体状态设置为 Modified (具有上述缺点)。请参阅我的答案此处,以获取有关此内容的更多讨论。

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.

这篇关于实体框架的通用插入或更新的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆