0

前段时间,我想实现一个能够确定是否对给定实体进行插入或更新的方法,因此我不必公开“插入”和“更新”方法,而只是一个简单的“插入或更新” ”。

确定实体是否是新实体的代码部分是:

    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 个对象,第一个将导致“新”,但接下来的九个将导致“已经存在”。这是因为 EF 的“查找”方法从 objectcontext 中读取数据,并且只有当它不存在时才会下到数据库进行查询。

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

现在,我知道数据库生成的 id 是邪恶的,绝对不适合任何 ORM,但我坚持使用这些方法,我需要修复此方法或完全删除它并退回到单独的“插入”和“更新”方法并将任务委托给调用者以确定要做什么。由于我们有一个高度解耦的解决方案,我宁愿避免这样做。

如果有人可以提供帮助并找到修复 GetEntityByPrimaryKey 方法的方法,那就太棒了。

谢谢。

4

3 回答 3

2

我有以下建议:

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

  1. IsTransient== 真的吗?-> 插入
  2. IsTransient== 假的?-> 带有数据库检查的现有代码

使该属性虚拟化,您甚至可以通过覆盖来支持具有“奇怪”PK 的实体IsTransient

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;
}
于 2013-02-21T10:07:52.183 回答
0

因为对于我要插入的所有对象,我在代码中的“Id”将为 0

当您不提供密钥时,您似乎期望密钥具有唯一性。是否可以将它们初始化为唯一的负数?(对于实际数据库条目来说不是有效值)

我有一个类似的问题(即自我跟踪对象能够判断两个尚未插入的子对象是否在键上相同......),这解决了它。

于 2013-02-21T10:32:43.053 回答
0

鉴于您的 POCO 没有 DB 垃圾,您必须使用 Fluent API 来声明 DB Generated Key 信息。所以也许 CONTEXT 上的 DbSet 包含这个 DB Generated 标志。

我使用扩展来获取上下文中使用的所有 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;
 }
}   
于 2013-02-21T16:32:47.900 回答