0

少し前に、特定のエンティティに対して挿入または更新を行うかどうかを判断できるメソッドを実装したかったので、"Insert" メソッドと "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」シナリオではひどく壊れており、その理由は非常に単純です。コード内の「ID」は、挿入するすべてのオブジェクトに対して 0 になるため、メソッドはそれらを考慮します。同じ。10 個のオブジェクトを追加すると、最初のオブジェクトは「新規」になりますが、次の 9 個は「既存のもの」になります。これは、EF の "Find" メソッドが objectcontext からデータを読み取り、それが存在しない場合にのみデータベースにアクセスしてクエリを実行するためです。

最初のオブジェクトの後、ID 0 の特定のタイプのエンティティが追跡されます。連続して呼び出すと「更新」が行われますが、これは間違っています。

データベースで生成された ID は悪であり、どの ORM にもまったく適していないことはわかっていますが、私はそれらに固執しており、このメソッドを修正するか、完全に削除して別の「挿入」メソッドと「更新」メソッドにフォールバックする必要があります。何をすべきかを決定するタスクを発信者に委任します。高度に分離されたソリューションがあるため、これを行うことは避けたいと思います。

GetEntityByPrimaryKey メソッドを修正する方法を見つけて助けてくれる人がいれば、それは素晴らしいことです。

ありがとう。

4

3 に答える 3

2

次の提案があります。

1:エンティティにプロパティの
ようなものを追加します。PK が 0 の場合は を返し、それ以外の場合は を返しIsTransientます。 このプロパティを使用して、次のようにメソッドを変更できます。truefalse

  1. IsTransient== 本当ですか? -> 挿入
  2. IsTransient== 嘘?-> データベース チェックを含む既存のコード

そのプロパティを virtual にすると、オーバーライドすることで「奇妙な」PK を持つエンティティをサポートすることもできますIsTransient

2:
これをエンティティに追加したくない場合でも、このロジックをカプセル化する拡張メソッドを作成できます。または、そのチェックを に直接追加することもできますInsertOrUpdate

エンティティに共通の基本クラスがないため、これらの提案は少し面倒になります。基本的に、エンティティごとに 1 つの拡張メソッドが必要です。

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

POCO に DB 汚物がない場合、Fluent API を使用して DB 生成キー情報を宣言している必要があります。したがって、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 に答える