2012-03-28 14 views
1

EF 4.1を使用して、一度に2百万行以上のトランザクションでラップされた多数のデータを挿入しています。今私はUPDATEロジックを追加したいと思います。大量のデータがあれば、変更トラッキングは無効になっています。私の頭の上から、私はこのような何かをしたい:EF 4.1挿入/更新ロジックのベストプラクティス

// Obviously simplified code... 
public void AddOrUpdate(Foo foo) 
{ 
    if(!db.Foos.Any(x => someEqualityTest(foo))) 
    { 
     db.Foos.Add(foo); 
    } 

    else 
    { 
     var f = db.Foos.First(x => someEqualityTest(foo)); 
     f = foo; 
    } 

    db.SaveChanges(); 
} 

これを改善する方法、おそらく上の任意のアイデアは?

+0

はあなたがチェック方法についての少しより多くの光を当てることができれば2つのインスタンスのフーは等しいですか?単純なIDの比較ですか? –

+0

私はちょうど詳細を一般化しようとしていましたが、ソリューションのより大きな論理的側面に焦点を当てました – Didaxis

答えて

2

私は、挿入物を更新と別にしておきます。

インサートの場合は、SqlBulkCopyを使用して、まだ存在しないすべてのレコードを挿入することをお勧めします。これはの方法になります。

まず、あなたのDbContextで一括挿入方法:

public class YourDbContext : DbContext 
{ 
    public void BulkInsert<T>(string tableName, IList<T> list) 
    { 
     using (var bulkCopy = new SqlBulkCopy(base.Database.Connection)) 
     { 
      bulkCopy.BatchSize = list.Count; 
      bulkCopy.DestinationTableName = tableName; 

      var table = new DataTable(); 
      var props = TypeDescriptor.GetProperties(typeof(T)) 
          // Dirty hack to make sure we only have system 
          // data types (i.e. filter out the 
          // relationships/collections) 
          .Cast<PropertyDescriptor>() 
          .Where(p => "System" == p.PropertyType.Namespace) 
          .ToArray(); 

      foreach (var prop in props) 
      { 
       bulkCopy.ColumnMappings.Add(prop.Name, prop.Name); 

       var type = Nullable.GetUnderlyingType(prop.PropertyType) 
          ?? prop.PropertyType; 

       table.Columns.Add(prop.Name, type); 
      } 

      var values = new object[props.Length]; 
      foreach (var item in list) 
      { 
       for (var i = 0; i < values.Length; i++) 
       { 
        values[i] = props[i].GetValue(item); 
       } 

       table.Rows.Add(values); 
      } 

      bulkCopy.WriteToServer(table); 
     } 
    } 
} 

次に、あなたの挿入/更新のために:

public void AddOrUpdate(IList<Foo> foos) 
{ 
    var foosToUpdate = db.Foos.Where(x => foos.Contains(x)).ToList(); 

    var foosToInsert = foos.Except(foosToUpdate).ToList(); 

    foreach (var foo in foosToUpdate) 
    { 
     var f = db.Foos.First(x => someEqualityTest(x)); 

     // update the existing foo `f` with values from `foo` 
    } 

    // Insert the new Foos to the table named "Foos" 
    db.BulkInsert("Foos", foosToinsert); 

    db.SaveChanges(); 
} 
+0

ありがとうございます、私はプロバイダベースのソリューションを作成しました。実際に現在のプロバイダはSqlBulkCopy(挿入のみ)を採用しています。私はEFプロバイダを再訪しています。なぜなら、挿入/更新ロジックも同様にしたいからです。しかし、あなたは非常に巧妙なソリューションを提供する、私はそれが好き! – Didaxis

+0

交換回答。私はあなたのソリューションはここに正しいアプローチだと思う – Didaxis

+0

ありがとう!お役に立てて嬉しいです。 –

1

あなたの更新...

var f = db.Foos.First(x => someEqualityTest(foo)); 
f = foo; 

...あなたがすべてでロードされたと添付オブジェクトfを変更していないので、あなただけの分離オブジェクトfooで変数fを上書き動作しません。添付されたオブジェクトはまだ文脈の中にありますが、ロード後に変更されておらず、変数を指していません。 SaveChangesはこの場合何も行いません。

「標準オプションは、」あなたはしている:

var f = db.Foos.First(x => someEqualityTest(foo)); 
db.Entry(f).State = EntityState.Modified; 

か、単に

db.Entry(foo).State = EntityState.Modified; 
// attaches as Modified, no need to load f 

これは修正されたすべてのプロパティをマークしていない - に関係なく、彼らが実際に変更された場合やない - とUPDATEを送信します各列ごとにデータベースに追加します。

修正としてだけは本当に変更されたプロパティをマークし、変更されただけの列に対してUPDATEを送信する第二の選択肢:今

var f = db.Foos.First(x => someEqualityTest(foo)); 
db.Entry(f).CurrentValues.SetValues(foo); 

、200万オブジェクトとあなたは「標準を持っていない更新します両方のオプション、特に、ソースオブジェクトとターゲットオブジェクトのプロパティ名と一致するように内部的にリフレクションを使用する可能性のある2つ目のオプションが遅すぎる可能性があります。

更新のパフォーマンスに関する最良の選択肢は、変更追跡プロキシです。これは、エンティティクラスのすべてのプロパティをvirtual(ナビゲーションプロパティだけでなくスカラープロパティ)としてマークする必要があり、変更追跡プロキシの作成を無効にしないことを意味します(デフォルトで有効)。

データベースからオブジェクトfをロードすると、EFはレイジーローディングプロキシに似た動的プロキシオブジェクト(エンティティから派生したもの)を作成します。プロパティにはフラグが保持されるように各プロパティセッターにコードが注入されます変更されたかどうか。

プロキシによって提供される変更の追跡は、スナップショットベースの変更の追跡(SaveChangesまたはDetectChangesで行われます)よりもはるかに高速です。

変更追跡プロキシを使用すると、上記の2つのオプションが高速であるとはわかりません。パフォーマンスに関する追跡プロキシを変更する真の選択肢は存在しないオブジェクトの数千と同様の更新状況で私の経験では

var f = db.Foos.First(x => someEqualityTest(foo)); 
f.Property1 = foo.Property1; 
f.Property2 = foo.Property2; 
// ... 
f.PropertyN = foo.PropertyN; 

:あなたが最高のパフォーマンスを得るために、手動プロパティの割り当てを必要とすることも可能です。

+0

優秀な、思慮深い答え!ちょうど私が探していたものの種類! – Didaxis

+0

@ErOx:BTW:最後に1回の 'SaveChanges'コールで2百万のオブジェクトを正常に挿入しましたか?私はちょうど50万のオブジェクトの古い測定値を取得しました。私は 'SaveChanges'を一度だけ呼び出すとメモリ不足の例外がありました:http://stackoverflow.com/a/5942176/270591 – Slauma

+0

良いキャッチ - いいえ、私はあなたがリンクしている他の質問に非常によく似たたくさんの追加ごとに文脈をリサイクルしています。上記のコードは非常に単純化されています – Didaxis

関連する問題