4

アプリケーションには、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();

問題は、一括コピーの後、LocationsEF ORM の一部であるコレクション内のオブジェクトが変更されないことです (これは問題ありませんが、期待どおりです)。ただし、これらのオブジェクトで作業を続行するには、挿入された ID が必要です。

簡単な解決策は、データベースからすぐにデータを再度選択することです。手元にデータがあり、別のコレクションに再選択するだけです。

しかし、その解決策は間違っているように感じます.IDを挿入の一部として取得する方法はありません.

編集:簡単な解決策が機能します。EFに簡単に同期する方法については、以下の受け入れられた回答を参照してください。

たぶん、SqlBulkCopy を使用しないでください (最大約 1000 行を期待しています)。

注意してください、いくつかの関連するSOの質問と解決策は、すべてEFから離れているようです..

  1. SQL BulkCopy の後で PrimayKey ID を取得できますか?
  2. エンティティ フレームワークでの一括挿入のパフォーマンスの向上
  3. Entity Framework で挿入する最速の方法(これは、多くの保留中の挿入を伴う SaveChanges() のパフォーマンスに関するものであり、1000 秒の保留中の処理の最後ではなく、X 挿入ごとに呼び出す必要があります)
4

3 に答える 3

4

SQL-Server 2008 以降を使用している場合は、ストアド プロシージャを使用して目的の操作を実行できます。TYPESQL のデータ テーブルと同じを定義する必要があります。

CREATE TYPE dbo.YourType AS TABLE (ID INT, Column1 INT, Column2 VARCHAR(5)...)

次に、この型をストアド プロシージャに渡します。

CREATE PROCEDURE dbo.InsertYourType (@YourType dbo.YourType READONLY)
AS
    BEGIN
        DECLARE @ID TABLE (ID INT NOT NULL PRIMARY KEY)
        INSERT INTO YourTable (Column1, Column2...)
        OUTPUT inserted.ID INTO @ID
        SELECT  Column1, Column2...
        FROM    @YourType

        SELECT  *
        FROM    YourTable
        WHERE   ID IN (SELECT ID FROM @ID)

    END

これにより、挿入された行の ID が取得され、すべての新しい行が返されます。C# データテーブルが dbo.YourType の形式に準拠している限り、通常 SqlCommand にパラメーターを渡すのと同じ方法でこれを渡すことができます。

SqlCommand.Parameters.Add("@YourType", YourDataTable)

これはデータを再選択するというあなたの提案に似ていると思いますが、ID 列のみを使用しているため、選択は高速である必要があります。一括コピーではなく SQL 挿入を使用するという問題はまだありますが、EF の手続きベースのソリューションではなく、よりセットベースのソリューションに戻っています。これは、投稿したリンクの 1 つで受け入れられた回答と非常によく似ていますが、テーブル変数を使用していくつかの段階を削除しました。

于 2012-05-01T16:13:02.897 に答える
4

EF を介して行うことは、SqlBulkCopy ほど高速ではありません。実際、生の SQLINSERTはそれほど高速ではありません。したがって、場所を読み直す必要があります。MergeOption.OverwriteChangesで再読み込みしてクエリを更新します。

于 2012-05-01T14:13:01.040 に答える