アプリケーションには、100 ~ 1000 行の挿入を引き起こすリクエストをユーザーがトリガーするユースケースがあります。
その挿入後、処理を続行し、最初に挿入されたオブジェクトへの外部キーであるオブジェクトをさらに作成するためにオブジェクトが必要です。つまり、挿入されたオブジェクトの主キー ID が必要です。
これまで、EF を使用して foreach ループでこれを行ってきましたが、これは遅すぎて、約 600 行を完了するのに約 15 ~ 20 秒かかりました。(ユーザーをブロックしている間、悪い:( )
元のコード (更新も処理しますが、パフォーマンスは気にしません。ユーザーをブロックしていません):
foreach (Location updatedLoc in locationsLoaded)
{
// find it in the collection from the database
Location fromDb = existingLocations.SingleOrDefault(loc => loc.ExtId.Equals(updatedLoc.ExtId));
// update or insert
if (fromDb != null)
{
// link ids for update
updatedLoc.Id = fromDb.Id;
// set values for update
db.Entry(fromDb).CurrentValues.SetValues(updatedLoc);
}
else
{
System.Diagnostics.Trace.WriteLine("Adding new location: " + updatedLoc.Name, "loadSimple");
// insert a new location <============ This is the bottleneck, takes about 20-40ms per row
db.Locations.Add(updatedLoc);
}
}
// This actually takes about 3 seconds for 600 rows, was actually acceptable
db.SaveChanges();
SOとインターネットで調査した後、EFを間違った方法で使用していて、使用する必要があることがわかりましたSqlBulkCopy
したがって、コードが書き直され、以前は最大 20 秒かかっていたものが、現在では最大 100 ミリ秒かかります (!)
foreach (Location updatedLoc in locationsLoaded)
{
// find it in the collection from the database
Location fromDb = existingLocations.SingleOrDefault(loc => loc.ExtId.Equals(updatedLoc.ExtId));
// update or insert
if (fromDb != null)
{
// link ids for update
updatedLoc.Id = fromDb.Id;
// set values for update
db.Entry(fromDb).CurrentValues.SetValues(updatedLoc);
}
else
{
System.Diagnostics.Trace.WriteLine("Adding new location: " + updatedLoc.Name, "loadSimple");
// insert a new location
dataTable.Rows.Add(new object[] { \\the 14 fields of the location.. });
}
}
System.Diagnostics.Trace.WriteLine("preparing to bulk insert", "loadSimple");
// perform the bulk insert
using (var bulkCopy = new System.Data.SqlClient.SqlBulkCopy(System.Configuration.ConfigurationManager.ConnectionStrings["bulk-inserter"].ConnectionString))
{
bulkCopy.DestinationTableName = "Locations";
for (int i = 0; i < dataTable.Columns.Count; i++)
{
bulkCopy.ColumnMappings.Add(i, i + 1);
}
bulkCopy.WriteToServer(dataTable);
}
// for update
db.SaveChanges();
問題は、一括コピーの後、Locations
EF ORM の一部であるコレクション内のオブジェクトが変更されないことです (これは問題ありませんが、期待どおりです)。ただし、これらのオブジェクトで作業を続行するには、挿入された ID が必要です。
簡単な解決策は、データベースからすぐにデータを再度選択することです。手元にデータがあり、別のコレクションに再選択するだけです。
しかし、その解決策は間違っているように感じます.IDを挿入の一部として取得する方法はありません.
編集:簡単な解決策が機能します。EFに簡単に同期する方法については、以下の受け入れられた回答を参照してください。
たぶん、SqlBulkCopy を使用しないでください (最大約 1000 行を期待しています)。
注意してください、いくつかの関連するSOの質問と解決策は、すべてEFから離れているようです..
- SQL BulkCopy の後で PrimayKey ID を取得できますか?
- エンティティ フレームワークでの一括挿入のパフォーマンスの向上
- Entity Framework で挿入する最速の方法(これは、多くの保留中の挿入を伴う SaveChanges() のパフォーマンスに関するものであり、1000 秒の保留中の処理の最後ではなく、X 挿入ごとに呼び出す必要があります)