实体框架按主键获取实体

本文关键字:实体 获取 框架 | 更新日期: 2023-09-27 18:00:26

前段时间,我想实现一个方法,该方法能够确定是对给定实体执行插入还是更新,所以我不必公开"insert"answers"update"方法,而只需要一个简单的"InsertOrUpdate"。

代码中发现实体是否为新实体的部分是:

    public virtual T GetEntityByPrimaryKey<T>(T entity) where T : class
    {
        var entityType = entity.GetType();
        var objectSet = ((IObjectContextAdapter)this.DatabaseContext).ObjectContext.CreateObjectSet<T>();
        var keyNames = objectSet.EntitySet.ElementType.KeyMembers.Select(edmMember => edmMember.Name);
        var keyValues = keyNames.Select(name => entityType.GetProperty(name).GetValue(entity, null)).ToArray();
        return this.DatabaseContext.Set<T>().Find(keyValues);
    }

InsertOrUpdate方法是这样的:

    public virtual T InsertOrUpdate<T>(T entity) where T : class
    {
        var databaseEntity = this.GetEntityByPrimaryKey(entity);
        if (databaseEntity == null)
        {
            var entry = this.DatabaseContext.Entry(entity);
            entry.State = EntityState.Added;
            databaseEntity = entry.Entity;
        }
        else
        {
            this.DatabaseContext.Entry(databaseEntity).CurrentValues.SetValues(entity);
        }
        return databaseEntity;
    }

现在,只要对象的"主键"是由代码决定的,这种方法就可以发挥神奇的作用。有效的例子有GUID、HI-LO算法、自然密钥等。

然而,对于"数据库生成的标识"场景来说,这是非常糟糕的,原因很简单:由于我在代码中的"Id"对于我要插入的所有对象都是0,因此该方法会认为它们是相同的。如果我添加了10个对象,第一个对象的结果将是"新的",但接下来的9个对象将导致"已经存在"。这是因为EF的"Find"方法从对象上下文中读取数据,只有当数据不存在时,它才会下到数据库中进行查询。

在第一个对象之后,将跟踪Id为0的给定类型的实体。连续调用将导致"更新",这是错误的。

现在,我知道数据库生成的id是邪恶的,对任何ORM都绝对不好,但我一直坚持这些方法,我需要修复这个方法,或者完全删除它,然后返回到分离的"Insert"answers"Update"方法,并将任务委托给调用者来决定该做什么。由于我们有一个高度解耦的解决方案,我宁愿避免这样做。

如果有人能帮助并找到修复GetEntityByPrimaryKey方法的方法,那将是非常棒的。

谢谢。

实体框架按主键获取实体

我有以下建议:

1:
我会向实体添加类似IsTransient属性的内容。如果PK为0,则返回true,否则返回false
您可以使用此属性更改您的方法,如下所示:

  1. IsTransient==真?->插入
  2. IsTransient==错误?->您的现有代码与数据库检查

使该属性成为虚拟属性,您甚至可以通过重写IsTransient来支持具有"奇怪"PK的实体。

2:
如果您不喜欢将其添加到实体中,您仍然可以创建一个封装此逻辑的扩展方法。甚至可以将该支票直接添加到您的InsertOrUpdate中。

由于您的实体没有一个公共基类,这些建议将变得有点乏味。基本上,每个实体都必须有一个扩展方法。

3:
如果您有PK的约定,您可以使用dynamic访问ID属性:

dynamic dynamicEntity = entity;
if(dynamicEntity.Id == 0)
{
    // Insert
}
else
{
    // Current code.
}

4:
考虑到向上下文中添加一个瞬态实体会破坏所有后续瞬态项,将瞬态项添加到列表中而不是上下文中可能是个好主意
只有在要提交时才将它们添加到上下文中。我相信有一个挂钩,你可以使用:

List<object> _newEntities;
private override OnCommit()
{
    foreach(var newEntity in newEntities)
        DatabaseContext.Entry(newEntity).State = EntityState.Added;
}
public virtual T InsertOrUpdate<T>(T entity) where T : class
{
    var databaseEntity = this.GetEntityByPrimaryKey(entity);
    if (databaseEntity == null)
        _newEntities.Add(entity);
    else
        this.DatabaseContext.Entry(databaseEntity).CurrentValues.SetValues(entity);
    return databaseEntity;
}

因为对于我要访问的所有对象,我在代码中的"Id"都将为0插入

当你没有提供钥匙时,你似乎在期待钥匙的独特性。是否可以将它们初始化为唯一的负数?(对于实际数据库条目来说,这不是一个有效值)

我遇到了一个类似的问题(即自跟踪对象能够判断两个尚未插入的子对象是否在密钥方面相同…),这解决了它。

如果您的POCO没有DB污垢,您必须使用Fluent API声明DB生成的密钥信息。因此,也许上下文中的数据库集包含这个DB生成的标志。

我使用扩展来获得上下文中使用的所有POCO。也许有了足够的反射,您可以找到一个有用的属性或属性作为DB生成的标志。剩下的就已经清楚了。

也许这是一个有用的起点:

 public static List<string> GetModelNames(this DbContext context ) {
      var model = new List<string>();
      var propList = context.GetType().GetProperties();
      foreach (var propertyInfo in propList)
      {
      if (propertyInfo.PropertyType.GetTypeInfo().Name.StartsWith("DbSet"))
      {
          model.Add(propertyInfo.Name);
          var innerProps = propertyInfo.GetType().GetProperties(); // added to snoop around in debug mode , can your find anything useful?
      }
      }

      return model;
  }
 public static List<string> GetModelTypes(this DbContext context)
 {
     var model = new List<string>();
     var propList = context.GetType().GetProperties();
     foreach (var propertyInfo in propList)
     {
         if (propertyInfo.PropertyType.GetTypeInfo().Name.StartsWith("DbSet"   ))
         {
             model.Add(propertyInfo.PropertyType.GenericTypeArguments[0].Name);
         }
     }

     return model;
 }
}