Entity Frameworkに挿入する最速の方法


681

Entity Frameworkに挿入する最速の方法を探しています。

アクティブなTransactionScopeがあり、挿入が巨大(4000以上)であるというシナリオのために、私はこれを求めています。それは潜在的に10分(トランザクションのデフォルトのタイムアウト)より長く続く可能性があり、これは不完全なトランザクションにつながります。


1
現在、どのようにしていますか?
ダスティンレイン、

TransactionScopeを作成し、DBContextをインスタンス化し、接続を開き、for-eachステートメントで挿入とSavingChanges(レコードごとに)を実行しています。注:TransactionScopeとDBContextはステートメントを使用しており、最後に接続を閉じていますブロック
Bongo Sharp、

参照のためのもう一つの答え:stackoverflow.com/questions/5798646/...
ラディスラフMrnka

2
SQLデータベースに挿入する最速の方法はEFを含みません。AFAIKそのBCPの次にTVP + Merge / insert。
StingyJack

1
コメントを読む人のために:最も当てはまる最新の答えがここにあります。
Tanveer Badar

回答:


985

あなたの質問へのコメントであなたの発言に:

"... SavingChanges(レコードごと)..."

それはあなたができる最悪のことです!SaveChanges()各レコードを呼び出すと、一括挿入が非常に遅くなります。パフォーマンスを向上させる可能性が高いいくつかの簡単なテストを行います。

  • SaveChanges()すべてのレコードの後で1回呼び出します。
  • SaveChanges()たとえば100レコード後に呼び出します。
  • SaveChanges()たとえば100レコードの後に呼び出し、コンテキストを破棄して新しいコンテキストを作成します。
  • 変更検出を無効にする

一括挿入の場合、私は次のようなパターンで作業して実験しています。

using (TransactionScope scope = new TransactionScope())
{
    MyDbContext context = null;
    try
    {
        context = new MyDbContext();
        context.Configuration.AutoDetectChangesEnabled = false;

        int count = 0;            
        foreach (var entityToInsert in someCollectionOfEntitiesToInsert)
        {
            ++count;
            context = AddToContext(context, entityToInsert, count, 100, true);
        }

        context.SaveChanges();
    }
    finally
    {
        if (context != null)
            context.Dispose();
    }

    scope.Complete();
}

private MyDbContext AddToContext(MyDbContext context,
    Entity entity, int count, int commitCount, bool recreateContext)
{
    context.Set<Entity>().Add(entity);

    if (count % commitCount == 0)
    {
        context.SaveChanges();
        if (recreateContext)
        {
            context.Dispose();
            context = new MyDbContext();
            context.Configuration.AutoDetectChangesEnabled = false;
        }
    }

    return context;
}

560.000エンティティ(9つのスカラープロパティ、ナビゲーションプロパティなし)をDBに挿入するテストプログラムがあります。このコードを使用すると、3分未満で動作します。

パフォーマンスのためにはSaveChanges()、「多数」のレコード(「100」または「1000」程度)の後に呼び出すことが重要です。また、SaveChangesの後にコンテキストを破棄して新しいコンテキストを作成するパフォーマンスも向上します。これにより、すべてSaveChangesのエンティティからコンテキストがクリアされますが、エンティティはまだ状態のコンテキストにアタッチされていますUnchanged。段階的に挿入が遅くなるのは、コンテキスト内の接続されたエンティティのサイズの増加です。ですので、しばらくしてからクリアすると便利です。

ここに私の560000エンティティのいくつかの測定があります:

  • commitCount = 1、recreateContext = false:長時間(これが現在の手順です)
  • commitCount = 100、recreateContext = false:20分以上
  • commitCount = 1000、recreateContext = false:242秒
  • commitCount = 10000、recreateContext = false:202秒
  • commitCount = 100000、recreateContext = false:199秒
  • commitCount = 1000000、recreateContext = false:メモリ不足例外
  • commitCount = 1、recreateContext = true: 10分以上
  • commitCount = 10、recreateContext = true: 241秒
  • commitCount = 100、recreateContext = true: 164秒
  • commitCount = 1000、recreateContext = true: 191秒

上記の最初のテストの動作は、パフォーマンスが非常に非線形であり、時間の経過とともに極端に低下することです。(「何時間も」は概算です。このテストを終了したことはありません。20分後に50.000エンティティで停止しました。)この非線形動作は、他のすべてのテストではそれほど重要ではありません。


89
@Bongo Sharp:DbContextを設定AutoDetectChangesEnabled = false;することを忘れないでください。:それはまた大きな追加のパフォーマンスへの影響があるstackoverflow.com/questions/5943394/...
Slauma

6
ええ、問題は、Entity Framework 4を使用していて、AutoDetectChangesEnabledが4.1の一部であるにもかかわらず、パフォーマンステストを実行したところ、驚くべき結果が得られたため、00:12:00から00:00:22になりましたSavinChanges各エンティティでolverloadを実行していた...あなたのanswareに感謝します!これが私が探していたものです
Bongo Sharp

10
context.Configuration.AutoDetectChangesEnabled = falseをありがとうございます。ヒント、それは大きな違いを生みます。
ドゥグラーツ

1
@ dahacker89:あなたは正しいバージョンのEF> = 4.1を使用しておりDbContext、NOT ObjectContext
スラウマ

3
@ dahacker89:私はあなたがおそらくもっと詳細にあなたの問題のために別の質問を作成することを勧めます。何が悪いのか、ここではわかりません。
スラウマ

176

この組み合わせにより、速度が十分に向上します。

context.Configuration.AutoDetectChangesEnabled = false;
context.Configuration.ValidateOnSaveEnabled = false;

46
ValidateOnSaveEnabledを盲目的に無効にしないでください。その動作に依存している可能性があり、手遅れになるまでそれを認識しないでください。次に、コードの他の場所で検証を実行している可能性があり、EFを再度検証する必要はまったくありません。
ジェレミークック

1
私のテストでは、20.000行の節約が101秒から88秒に減少しました。それほど多くはなく、その影響は何ですか。
AH。

27
@JeremyCook私があなたが得ようとしているのは、これらのプロパティをデフォルト値から変更することの考えられる影響を説明すれば、この答えははるかに優れていると思います(パフォーマンスの向上は別として)。同意する。
疑似コーダー

1
これは私にとってはうまくいきましたが、コンテキストでレコードを更新する場合は、DetectChanges()を明示的に呼び出す必要があります
hillstuk

2
:これらは無効になり、その後のtry-finallyブロックで再度有効にすることができmsdn.microsoft.com/en-us/data/jj556205.aspx
yellavon

98

最速の方法は、一括挿入拡張機能を使用することです、私が開発することです

注:これは無料の製品ではなく、商用製品です

SqlBulkCopyとカスタムデータリーダーを使用して、最大のパフォーマンスを実現します。その結果、通常の挿入やAddRangeを使用するよりも20倍以上速くなります EntityFramework.BulkInsertとEF AddRange

使い方は非常に簡単です

context.BulkInsert(hugeAmountOfEntities);

10
高速ですが、階層の最上位レイヤーのみを実行します。
CADは、2015

65
無料ではありません。
アミールサニヤン2017年

72
広告は賢くなっています...これは有料の製品であり、フリーランスにとっては非常に高価です。警告してください!
JulioQc 2017年

35
1年間のサポートとアップグレードに600米ドルですか?あなたは正気ですか?
Camilo Terevinto 2017

7
imはもう製品の所有者ではありません
maxlego

83

System.Data.SqlClient.SqlBulkCopyこのための使用を検討する必要があります。ここにドキュメントがありますです。もちろん、オンラインにはたくさんのチュートリアルがあります。

申し訳ありませんが、EFで必要なことを実行するための簡単な答えを探していたと思いますが、バルク操作はORMが意図するものではありません。


1
私はこれを研究している間にSqlBulkCopyに何度か遭遇しましたが、それはテーブルからテーブルへの挿入に向いているようです、残念ながら私は簡単な解決策を期待していませんでしたが、たとえば、接続を手動で、EFに任せるのではなく
Bongo Sharp

7
SqlBulkCopyを使用して、アプリケーションから直接大量のデータを挿入しました。基本的には、DataTableを作成し、データを入力して、BulkCopyに渡す必要があります。DataTableを設定するときにいくつかの落とし穴がありますが(そのほとんどは残念ながら忘れてしまいました)、問題なく機能するはずです
Adam Rackis

2
私は概念実証を行いましたが、約束どおり、それは非常に高速に動作しますが、EFを使用している理由の1つは、リレーショナルデータの挿入がより簡単だからです。たとえば、リレーショナルデータが既に含まれているエンティティを挿入する場合、それも挿入されますが、このシナリオに巻き込まれたことがありますか?ありがとう!
Bongo Sharp、

2
あいにく、オブジェクトのWebをDBMSに挿入することは、BulkCopyが実際に行うことではありません。それがEFのようなORMの利点です。その代償として、数百の類似したオブジェクトグラフを効率的に実行するために拡張できません。
Adam Rackis、2011年

2
SqlBulkCopyは、未処理の速度が必要な場合、またはこの挿入を再実行する場合に間違いなく使用できる方法です。以前に数百万のレコードを挿入したことがあり、非常に高速です。つまり、この挿入を再実行する必要がない限り、EFを使用する方が簡単な場合があります。
ニール、

49

私はAdam Rackisに同意します。SqlBulkCopyあるデータソースから別のデータソースに一括レコードを転送する最も速い方法です。これを使用して2万件のレコードをコピーしました。3秒もかかりませんでした。以下の例をご覧ください。

public static void InsertIntoMembers(DataTable dataTable)
{           
    using (var connection = new SqlConnection(@"data source=;persist security info=True;user id=;password=;initial catalog=;MultipleActiveResultSets=True;App=EntityFramework"))
    {
        SqlTransaction transaction = null;
        connection.Open();
        try
        {
            transaction = connection.BeginTransaction();
            using (var sqlBulkCopy = new SqlBulkCopy(connection, SqlBulkCopyOptions.TableLock, transaction))
            {
                sqlBulkCopy.DestinationTableName = "Members";
                sqlBulkCopy.ColumnMappings.Add("Firstname", "Firstname");
                sqlBulkCopy.ColumnMappings.Add("Lastname", "Lastname");
                sqlBulkCopy.ColumnMappings.Add("DOB", "DOB");
                sqlBulkCopy.ColumnMappings.Add("Gender", "Gender");
                sqlBulkCopy.ColumnMappings.Add("Email", "Email");

                sqlBulkCopy.ColumnMappings.Add("Address1", "Address1");
                sqlBulkCopy.ColumnMappings.Add("Address2", "Address2");
                sqlBulkCopy.ColumnMappings.Add("Address3", "Address3");
                sqlBulkCopy.ColumnMappings.Add("Address4", "Address4");
                sqlBulkCopy.ColumnMappings.Add("Postcode", "Postcode");

                sqlBulkCopy.ColumnMappings.Add("MobileNumber", "MobileNumber");
                sqlBulkCopy.ColumnMappings.Add("TelephoneNumber", "TelephoneNumber");

                sqlBulkCopy.ColumnMappings.Add("Deleted", "Deleted");

                sqlBulkCopy.WriteToServer(dataTable);
            }
            transaction.Commit();
        }
        catch (Exception)
        {
            transaction.Rollback();
        }

    }
}

1
この投稿で提供されているソリューションの多くを試してみましたが、SqlBulkCopyが圧倒的に高速でした。純粋なEFには15分かかりましたが、ソリューションとSqlBulkCopyを組み合わせることで、1.5分に短縮できました。これは200万件のレコードでした!DBインデックスの最適化なし。
jonas

リストはDataTableよりも簡単です。AsDataReader()この回答で説明されている拡張メソッドがあります:stackoverflow.com/a/36817205/1507899
RJB

しかし、その唯一の上位エンティティはリレーショナルエンティティではなく
Zahid Mustafa

1
@ZahidMustafa:ええ。Bulk-Analysis-And-Relation-Tracing-On-Object-Graphsではなく、BulkInsertを実行しています。関係をカバーする場合は、挿入順序を分析および決定してから、個々のレベルを一括挿入し、一部のキーを次のように更新する必要があります。必要に応じて、スピーディなカスタムテーラードソリューションを入手できます。または、EFを使用してそれを行うこともできます。あなたの側で何もする必要はありませんが、実行時は遅くなります。
ケツァルコアトル2017年

23

EFを使用して一括挿入を行う方法に関するこの記事をお勧めします。

Entity Frameworkと遅い一括INSERT

彼はこれらの領域を調査し、パフォーマンスを比較します。

  1. デフォルトのEF(30,000レコードの追加を完了するまで57分)
  2. ADO.NETコードで置き換える(同じ30,000の場合は25
  3. コンテキストブロート-作業ユニットごとに新しいコンテキストを使用して、アクティブなコンテキストグラフを小さく保ちます(同じ30,000回の挿入に33秒かかります)
  4. 大きなリスト-AutoDetectChangesEnabledをオフにします(時間を約20秒に減らします)
  5. バッチ処理(16秒まで)
  6. DbTable.AddRange()-(パフォーマンスは12の範囲です)

21

ここで言及されたことがないので、EFCore.BulkExtensionsをここでコメントします

context.BulkInsert(entitiesList);                 context.BulkInsertAsync(entitiesList);
context.BulkUpdate(entitiesList);                 context.BulkUpdateAsync(entitiesList);
context.BulkDelete(entitiesList);                 context.BulkDeleteAsync(entitiesList);
context.BulkInsertOrUpdate(entitiesList);         context.BulkInsertOrUpdateAsync(entitiesList);         // Upsert
context.BulkInsertOrUpdateOrDelete(entitiesList); context.BulkInsertOrUpdateOrDeleteAsync(entitiesList); // Sync
context.BulkRead(entitiesList);                   context.BulkReadAsync(entitiesList);

1
私はこの提案を2番目に述べます。多くの自作ソリューションを試した後、これは私の挿入を50秒以上から1秒に短縮しました。そして、それは非常に簡単に組み込むことができるMITライセンスです。
SouthShoreAK

これはef 6.xのアベイルズです
Alok

これは、10以上のエンティティかどうAddRangeを使用するよりも唯一のよりパフォーマンスである
ジャッカル

5
1万回の挿入が9分から12秒になりました。これはもっと注目に値します!
カリスト

2
受け入れられた回答を変更する方法がある場合、これが現在の受け入れられた回答になるはずです。そして、EFチームがすぐにこれを提供してくれることを願っています。
Tanveer Badar

18

私はスラウマの答えを調査しました(これは素晴らしいです、アイデアマンに感謝します)。最適な速度に達するまでバッチサイズを減らしました。スラウマの結果を見る:

  • commitCount = 1、recreateContext = true:10分以上
  • commitCount = 10、recreateContext = true:241秒
  • commitCount = 100、recreateContext = true:164秒
  • commitCount = 1000、recreateContext = true:191秒

1から10、10から100に移動すると速度が向上することがわかりますが、100から1000に挿入すると速度が再び低下します。

だから私はあなたが10から100の間のどこかでバッチサイズを値に減らすときに何が起こっているかに焦点を当てており、これが私の結果です(私は異なる行コンテンツを使用しているので、私の時間は異なる値です):

Quantity    | Batch size    | Interval
1000    1   3
10000   1   34
100000  1   368

1000    5   1
10000   5   12
100000  5   133

1000    10  1
10000   10  11
100000  10  101

1000    20  1
10000   20  9
100000  20  92

1000    27  0
10000   27  9
100000  27  92

1000    30  0
10000   30  9
100000  30  92

1000    35  1
10000   35  9
100000  35  94

1000    50  1
10000   50  10
100000  50  106

1000    100 1
10000   100 14
100000  100 141

私の結果に基づくと、実際の最適値はバッチサイズでおよそ30です。それは10と100の両方よりも小さいです。問題は、なぜ30が最適であるのかわからないことです。


2
Postrgesと純粋なSQL(EFではなくSQLに依存する)でも、30が最適であることがわかりました。
カミルガリーエフ2016年

私の経験では、接続速度や行のサイズによって最適な値は異なります。高速接続と小さな列の場合、最適なのは200行以上です。
ジン

18

他の人が言ったように、SqlBulkCopyは、本当に良い挿入パフォーマンスが必要な場合に行う方法です。

実装が少し面倒ですが、それを支援するライブラリがあります。いくつかありますが、今回は自分のライブラリを恥知らずにプラグインしますhttps : //github.com/MikaelEliasson/EntityFramework.Utilities#batch-insert-entities

必要なコードは次のとおりです。

 using (var db = new YourDbContext())
 {
     EFBatchOperation.For(db, db.BlogPosts).InsertAll(list);
 }

それで、それはどれくらい速いですか?非常に多くの要因、コンピューターのパフォーマンス、ネットワーク、オブジェクトサイズなどに依存するため、非常に難しいです。私が行ったパフォーマンステストでは、25,000のエンティティをlocalhost の標準的な方法でおよそ10秒で挿入できることを示唆しています。他の回答で述べた。EFUtilitiesでは約300msかかります。さらに興味深いのは、この方法を使用して15秒未満で約300万のエンティティを保存し、1秒あたり約20万のエンティティを平均化したことです。

関連するデータを挿入する必要がある場合、1つの問題は当然です。これは、上記の方法を使用してSQLサーバーで効率的に実行できますが、外部キーを設定できるように、親のアプリコードでIDを生成できるID生成戦略が必要です。これは、GUIDまたはHiLo id生成などを使用して実行できます。


うまくいきます。ただし、構文は少し冗長です。すべての静的メソッドにEFBatchOperation渡すのではDbContextなく、toに渡すコンストラクターがあるとよいと思います。InsertAllおよびのUpdateAllような、コレクションを自動的に見つけるのジェネリックバージョンDbContext.Set<T>も良いでしょう。
kjbartel 2015年

感謝を言うための簡単なコメント!このコードにより、1.5秒で170kのレコードを保存できました。私が試した他の方法を水から完全に吹き飛ばします。
トム・グレン・

@Mikael 1つの問題はIDフィールドを扱うことです。ID挿入を有効にする方法はありますか?
ジョーフィリップス

1
EntityFramework.BulkInsertとは対照的に、このライブラリは無料のままでした。+1
Rudey

14

Dispose()コンテキスト内のAdd()他のプリロードされたエンティティ(ナビゲーションプロパティなど)に依存しているエンティティは、コンテキストによって問題が発生します。

同じパフォーマンスを実現するために、同様のコンセプトを使用してコンテキストを小さくします

しかしDispose()、コンテキストと再作成の代わりに、すでに存在しているエンティティを切り離すだけですSaveChanges()

public void AddAndSave<TEntity>(List<TEntity> entities) where TEntity : class {

const int CommitCount = 1000; //set your own best performance number here
int currentCount = 0;

while (currentCount < entities.Count())
{
    //make sure it don't commit more than the entities you have
    int commitCount = CommitCount;
    if ((entities.Count - currentCount) < commitCount)
        commitCount = entities.Count - currentCount;

    //e.g. Add entities [ i = 0 to 999, 1000 to 1999, ... , n to n+999... ] to conext
    for (int i = currentCount; i < (currentCount + commitCount); i++)        
        _context.Entry(entities[i]).State = System.Data.EntityState.Added;
        //same as calling _context.Set<TEntity>().Add(entities[i]);       

    //commit entities[n to n+999] to database
    _context.SaveChanges();

    //detach all entities in the context that committed to database
    //so it won't overload the context
    for (int i = currentCount; i < (currentCount + commitCount); i++)
        _context.Entry(entities[i]).State = System.Data.EntityState.Detached;

    currentCount += commitCount;
} }

try catchでラップし、TrasactionScope()必要に応じて、コードをクリーンに保つためにここに表示しない


1
そのため、Entity Framework 6.0を使用した挿入(AddRange)の速度が低下しました。20.000行の挿入は、約101秒から118秒に増加しました。
AH。

1
@Stephen Ho:私は自分のコンテキストを破棄しないようにしています。これはコンテキストを再作成するよりも遅いことは理解できますが、コンテキストを再作成せずにcommitCountを設定した場合よりも速くこれが見つかったかどうかを知りたいです。
学習者

@学習者:コンテキストを再作成するよりも速かったと思います。しかし、私は今、ようやくSqlBulkCopyを使用するように切り替えたのを本当に覚えていません。
Stephen Ho

何らかの奇妙な理由により、すべてをusingステートメントでラップし、DbContextでDispose()を呼び出したとしても、whileループの2番目のパスでいくつかの残されたトラッキングが発生したため、この手法を使用する必要がありました。 。(2番目のパスで)コンテキストに追加すると、コンテキストセットのカウントは1つではなく6にジャンプします。任意に追加された他の項目は、whileループの最初のパスですでに挿入されているため、(明らかな理由により)2番目のパスでSaveChangesの呼び出しが失敗します。
Hallmanac 14

9

これは非常に古い質問であることはわかっていますが、EFで一括挿入を使用する拡張メソッドを開発したとある人が言ったところ、チェックしたところ、今日のライブラリの価格は599ドル(開発者1人)であることがわかりました。多分それはライブラリ全体にとって理にかなっていますが、一括挿入だけではこれは多すぎます。

これは私が作った非常に単純な拡張メソッドです。私は最初にデータベースとペアで使用します(最初にコードでテストしないでくださいが、同じように機能すると思います)。YourEntitiesコンテキストの名前で変更します。

public partial class YourEntities : DbContext
{
    public async Task BulkInsertAllAsync<T>(IEnumerable<T> entities)
    {
        using (var conn = new SqlConnection(Database.Connection.ConnectionString))
        {
            await conn.OpenAsync();

            Type t = typeof(T);

            var bulkCopy = new SqlBulkCopy(conn)
            {
                DestinationTableName = GetTableName(t)
            };

            var table = new DataTable();

            var properties = t.GetProperties().Where(p => p.PropertyType.IsValueType || p.PropertyType == typeof(string));

            foreach (var property in properties)
            {
                Type propertyType = property.PropertyType;
                if (propertyType.IsGenericType &&
                    propertyType.GetGenericTypeDefinition() == typeof(Nullable<>))
                {
                    propertyType = Nullable.GetUnderlyingType(propertyType);
                }

                table.Columns.Add(new DataColumn(property.Name, propertyType));
            }

            foreach (var entity in entities)
            {
                table.Rows.Add(
                    properties.Select(property => property.GetValue(entity, null) ?? DBNull.Value).ToArray());
            }

            bulkCopy.BulkCopyTimeout = 0;
            await bulkCopy.WriteToServerAsync(table);
        }
    }

    public void BulkInsertAll<T>(IEnumerable<T> entities)
    {
        using (var conn = new SqlConnection(Database.Connection.ConnectionString))
        {
            conn.Open();

            Type t = typeof(T);

            var bulkCopy = new SqlBulkCopy(conn)
            {
                DestinationTableName = GetTableName(t)
            };

            var table = new DataTable();

            var properties = t.GetProperties().Where(p => p.PropertyType.IsValueType || p.PropertyType == typeof(string));

            foreach (var property in properties)
            {
                Type propertyType = property.PropertyType;
                if (propertyType.IsGenericType &&
                    propertyType.GetGenericTypeDefinition() == typeof(Nullable<>))
                {
                    propertyType = Nullable.GetUnderlyingType(propertyType);
                }

                table.Columns.Add(new DataColumn(property.Name, propertyType));
            }

            foreach (var entity in entities)
            {
                table.Rows.Add(
                    properties.Select(property => property.GetValue(entity, null) ?? DBNull.Value).ToArray());
            }

            bulkCopy.BulkCopyTimeout = 0;
            bulkCopy.WriteToServer(table);
        }
    }

    public string GetTableName(Type type)
    {
        var metadata = ((IObjectContextAdapter)this).ObjectContext.MetadataWorkspace;
        var objectItemCollection = ((ObjectItemCollection)metadata.GetItemCollection(DataSpace.OSpace));

        var entityType = metadata
                .GetItems<EntityType>(DataSpace.OSpace)
                .Single(e => objectItemCollection.GetClrType(e) == type);

        var entitySet = metadata
            .GetItems<EntityContainer>(DataSpace.CSpace)
            .Single()
            .EntitySets
            .Single(s => s.ElementType.Name == entityType.Name);

        var mapping = metadata.GetItems<EntityContainerMapping>(DataSpace.CSSpace)
                .Single()
                .EntitySetMappings
                .Single(s => s.EntitySet == entitySet);

        var table = mapping
            .EntityTypeMappings.Single()
            .Fragments.Single()
            .StoreEntitySet;

        return (string)table.MetadataProperties["Table"].Value ?? table.Name;
    }
}

次のIEnumerableように、から継承する任意のコレクションに対してそれを使用できます。

await context.BulkInsertAllAsync(items);

サンプルコードを完成させてください。どこがbulkCopy
Seabizkit

1
それは既にここにあります:await bulkCopy.WriteToServerAsync(table);
ギルヘルム2018

たぶん私ははっきりしていませんでした、あなたの書き込みで、あなたはあなたが拡張をしたことを提案します...私は実際に両方の方法でSqlBulkCopy libを使用するとき、私はサードパートのlibが必要ないことを意味したと考えました。これは完全にSqlBulkCopyに依存します。なぜ私がbulkCopyがどこから来たのかを尋ねたとき、あなたはその上に拡張ライブラリーを作成した拡張ライブラリーです。ここで私がSqlBulkCopy libをどのように使用したかをここで言うのがもっと理にかなっていますか
Seabizkit 2018

非同期バージョンではconn.OpenAsyncを使用する必要があります
Robert

6

挿入するデータのXMLを取得するストアドプロシージャを使用してみてください。


9
XMLとして保存したくない場合は、XMLとしてデータを渡す必要はありません。SQL 2008では、テーブル値パラメーターを使用できます。
Ladislav Mrnka、

私はこれを明確にしませんでしたが、SQL 2005もサポートする必要があります
Bongo Sharp

4

上記の@Slaumaの例の一般的な拡張を行いました。

public static class DataExtensions
{
    public static DbContext AddToContext<T>(this DbContext context, object entity, int count, int commitCount, bool recreateContext, Func<DbContext> contextCreator)
    {
        context.Set(typeof(T)).Add((T)entity);

        if (count % commitCount == 0)
        {
            context.SaveChanges();
            if (recreateContext)
            {
                context.Dispose();
                context = contextCreator.Invoke();
                context.Configuration.AutoDetectChangesEnabled = false;
            }
        }
        return context;
    }
}

使用法:

public void AddEntities(List<YourEntity> entities)
{
    using (var transactionScope = new TransactionScope())
    {
        DbContext context = new YourContext();
        int count = 0;
        foreach (var entity in entities)
        {
            ++count;
            context = context.AddToContext<TenancyNote>(entity, count, 100, true,
                () => new YourContext());
        }
        context.SaveChanges();
        transactionScope.Complete();
    }
}

4

Entity Frameworkに挿入する最速の方法を探しています

利用可能な一括挿入をサポートするサードパーティライブラリがいくつかあります。

  • Z.EntityFramework.Extensions(推奨
  • EFUtilities
  • EntityFramework.BulkInsert

参照:Entity Framework一括挿入ライブラリ

一括挿入ライブラリを選択するときは注意してください。Entity Framework Extensionsのみがあらゆる種類の関連付けと継承をサポートし、それはまだサポートされている唯一のものです。


免責事項:私はEntity Framework Extensionsの所有者です

このライブラリを使用すると、シナリオに必要なすべての一括操作を実行できます。

  • 一括保存変更
  • 一括挿入
  • 一括削除
  • 一括更新
  • 一括マージ

// Easy to use
context.BulkSaveChanges();

// Easy to customize
context.BulkSaveChanges(bulk => bulk.BatchSize = 100);

// Perform Bulk Operations
context.BulkDelete(customers);
context.BulkInsert(customers);
context.BulkUpdate(customers);

// Customize Primary Key
context.BulkMerge(customers, operation => {
   operation.ColumnPrimaryKeyExpression = 
        customer => customer.Code;
});

19
これはすばらしい拡張ですが、無料ではありません
Okan Kocyigit 2017

2
この答えはかなり良いもので、EntityFramework.BulkInsertは1.5秒で15K行の一括挿入を実行し、Windowsサービスのような内部プロセスに対しては非常にうまく機能します。
コルテス牧師

4
ええ、一括挿入には600ドル。それだけの価値があります。
eocron、

1
@eocron Yeatあなたがそれを商業的に使用するならそれは価値があります。自分で構築するのに何時間も費やす必要のないもので600ドルを超える問題が発生することはありません。はい、それはお金がかかりますが、私の時給を見ると、それはよく使うお金です!
ジョーディファンアイク

3

使用SqlBulkCopy

void BulkInsert(GpsReceiverTrack[] gpsReceiverTracks)
{
    if (gpsReceiverTracks == null)
    {
        throw new ArgumentNullException(nameof(gpsReceiverTracks));
    }

    DataTable dataTable = new DataTable("GpsReceiverTracks");
    dataTable.Columns.Add("ID", typeof(int));
    dataTable.Columns.Add("DownloadedTrackID", typeof(int));
    dataTable.Columns.Add("Time", typeof(TimeSpan));
    dataTable.Columns.Add("Latitude", typeof(double));
    dataTable.Columns.Add("Longitude", typeof(double));
    dataTable.Columns.Add("Altitude", typeof(double));

    for (int i = 0; i < gpsReceiverTracks.Length; i++)
    {
        dataTable.Rows.Add
        (
            new object[]
            {
                    gpsReceiverTracks[i].ID,
                    gpsReceiverTracks[i].DownloadedTrackID,
                    gpsReceiverTracks[i].Time,
                    gpsReceiverTracks[i].Latitude,
                    gpsReceiverTracks[i].Longitude,
                    gpsReceiverTracks[i].Altitude
            }
        );
    }

    string connectionString = (new TeamTrackerEntities()).Database.Connection.ConnectionString;
    using (var connection = new SqlConnection(connectionString))
    {
        connection.Open();
        using (var transaction = connection.BeginTransaction())
        {
            using (var sqlBulkCopy = new SqlBulkCopy(connection, SqlBulkCopyOptions.TableLock, transaction))
            {
                sqlBulkCopy.DestinationTableName = dataTable.TableName;
                foreach (DataColumn column in dataTable.Columns)
                {
                    sqlBulkCopy.ColumnMappings.Add(column.ColumnName, column.ColumnName);
                }

                sqlBulkCopy.WriteToServer(dataTable);
            }
            transaction.Commit();
        }
    }

    return;
}

3

リストを保存する最も速い方法の1つは、次のコードを適用する必要があります

context.Configuration.AutoDetectChangesEnabled = false;
context.Configuration.ValidateOnSaveEnabled = false;

AutoDetectChangesEnabled = false

Add、AddRange&SaveChanges:変更を検出しません。

ValidateOnSaveEnabled = false;

変更トラッカーを検出しません

nugetを追加する必要があります

Install-Package Z.EntityFramework.Extensions

これで、次のコードを使用できます

var context = new MyContext();

context.Configuration.AutoDetectChangesEnabled = false;
context.Configuration.ValidateOnSaveEnabled = false;

context.BulkInsert(list);
context.BulkSaveChanges();

一括更新のサンプルコードを使用できますか?
AminGolmahalle

4
Zライブラリは無料ではありません
SHADOW.NET

3

SqlBulkCopyは超高速です

これは私の実装です:

// at some point in my calling code, I will call:
var myDataTable = CreateMyDataTable();
myDataTable.Rows.Add(Guid.NewGuid,tableHeaderId,theName,theValue); // e.g. - need this call for each row to insert

var efConnectionString = ConfigurationManager.ConnectionStrings["MyWebConfigEfConnection"].ConnectionString;
var efConnectionStringBuilder = new EntityConnectionStringBuilder(efConnectionString);
var connectionString = efConnectionStringBuilder.ProviderConnectionString;
BulkInsert(connectionString, myDataTable);

private DataTable CreateMyDataTable()
{
    var myDataTable = new DataTable { TableName = "MyTable"};
// this table has an identity column - don't need to specify that
    myDataTable.Columns.Add("MyTableRecordGuid", typeof(Guid));
    myDataTable.Columns.Add("MyTableHeaderId", typeof(int));
    myDataTable.Columns.Add("ColumnName", typeof(string));
    myDataTable.Columns.Add("ColumnValue", typeof(string));
    return myDataTable;
}

private void BulkInsert(string connectionString, DataTable dataTable)
{
    using (var connection = new SqlConnection(connectionString))
    {
        connection.Open();
        SqlTransaction transaction = null;
        try
        {
            transaction = connection.BeginTransaction();

            using (var sqlBulkCopy = new SqlBulkCopy(connection, SqlBulkCopyOptions.TableLock, transaction))
            {
                sqlBulkCopy.DestinationTableName = dataTable.TableName;
                foreach (DataColumn column in dataTable.Columns) {
                    sqlBulkCopy.ColumnMappings.Add(column.ColumnName, column.ColumnName);
                }

                sqlBulkCopy.WriteToServer(dataTable);
            }
            transaction.Commit();
        }
        catch (Exception)
        {
            transaction?.Rollback();
            throw;
        }
    }
}

3

[2019年アップデート] EF Core 3.1

上記の説明に従って、EF CoreでAutoDetectChangesEnabledを無効にすることは完全に機能しました。挿入時間は100で除算されました(数分から数秒まで、クロステーブル関係のある10kレコード)。

更新されたコードは次のとおりです。

  context.ChangeTracker.AutoDetectChangesEnabled = false;
            foreach (IRecord record in records) {
               //Add records to your database        
            }
            context.ChangeTracker.DetectChanges();
            context.SaveChanges();
            context.ChangeTracker.AutoDetectChangesEnabled = true; //do not forget to re-enable

2

Entity Frameworkを使用した場合と実際の例でSqlBulkCopyクラスを使用した場合のパフォーマンスの比較を次に示します。SQLServer データベースに複雑なオブジェクトを一括挿入する方法

他の人がすでに強調したように、ORMは一括操作で使用することを意図していません。柔軟性、懸念事項の分離、その他の利点がありますが、一括操作(一括読み取りを除く)はその1つではありません。


2

別のオプションは、Nugetから入手可能なSqlBulkToolsを使用することです。それは非常に使いやすく、いくつかの強力な機能を備えています。

例:

var bulk = new BulkOperations();
var books = GetBooks();

using (TransactionScope trans = new TransactionScope())
{
    using (SqlConnection conn = new SqlConnection(ConfigurationManager
    .ConnectionStrings["SqlBulkToolsTest"].ConnectionString))
    {
        bulk.Setup<Book>()
            .ForCollection(books)
            .WithTable("Books") 
            .AddAllColumns()
            .BulkInsert()
            .Commit(conn);
    }

    trans.Complete();
}

その他の例と高度な使用法については、ドキュメントを参照してください。免責事項:私はこのライブラリの作者であり、どの見解も私自身の意見です。


2
このプロジェクトは、NuGetとGitHubの両方から削除されました。
0xced 2017

1

私の知識のとおりありno BulkInsertEntityFramework巨大な挿入のパフォーマンスを向上させます。

このシナリオでは、SqlBulkCopyを使用ADO.netして問題を解決できます


私はそのクラスを調べていましたが、テーブルからテーブルへの挿入に重点が置かれているようですね。
Bongo Sharp、

あなたが何を意味するのかわからない、それはWriteToServerをとる過負荷を持っていますDataTable
ブリンディ、

.NetオブジェクトからSQLにも挿入できます。何を探していますか?
anishMarokey

TransactionScopeブロック内のデータベースに潜在的に数千のレコードを挿入する方法
Bongo Sharp


1

バックグラウンドワーカーまたはタスクを介して挿入しようとしたことがありますか?

私の場合、(NavigationPropertiesによって)外部キー関係を持つ182の異なるテーブルに分散された7760レジスタを挿入しています。

タスクがなければ、2分半かかりました。タスク内(Task.Factory.StartNew(...)) 15秒かかりました。

SaveChanges()すべてのエンティティをコンテキストに追加した後にのみ実行します。(データの整合性を確保するため)


2
私は、コンテキストがスレッドセーフではないことを確信しています。すべてのエンティティが保存されたことを確認するためのテストはありますか?
ダニーVarod 2013年

エンティティフレームワーク全体がスレッドセーフではないことはわかっていますが、オブジェクトをコンテキストに追加し、最後に保存するだけです...ここで完全に機能します。
Rafael AMS

つまり、メインスレッドでDbContext.SaveChanges()を呼び出していますが、コンテキストへのエンティティの追加はバックグラウンドスレッドで実行されますよね?
Prokurors 2014年

1
はい、スレッド内にデータを追加します。すべてが完了するのを待ちます。メインスレッドの変更を保存して保存
Rafael AMS 2014

この方法は危険で間違いを起こしやすいと思いますが、非常に興味深い方法です。
学習者2014

1

SaveChanges()を実行すると、Insertステートメントが1つずつデータベースに送信されるため、ここで記述されているすべてのソリューションは役に立ちません。これが、エンティティの機能です。

たとえば、データベースへの往復が50ミリ秒である場合、挿入に必要な時間はレコード数x 50ミリ秒です。

BulkInsertを使用する必要があります。ここにリンクがあります:https ://efbulkinsert.codeplex.com/

挿入時間を5〜6分から10〜12秒に短縮しました。



1

[POSTGRESQLの新しい解決策]かなり古い投稿であることはわかっていますが、最近同様の問題が発生しましたが、Postgresqlを使用していました。効果的な一括挿入を使用したかったのですが、かなり難しいことがわかりました。このDBで適切な無料のライブラリを見つけることができません。私はこのヘルパーのみを見つけました:https : //bytefish.de/blog/postgresql_bulk_insert/これもNugetにあります。Entity Frameworkの方法でプロパティを自動マッピングする小さなマッパーを作成しました。

public static PostgreSQLCopyHelper<T> CreateHelper<T>(string schemaName, string tableName)
        {
            var helper = new PostgreSQLCopyHelper<T>("dbo", "\"" + tableName + "\"");
            var properties = typeof(T).GetProperties();
            foreach(var prop in properties)
            {
                var type = prop.PropertyType;
                if (Attribute.IsDefined(prop, typeof(KeyAttribute)) || Attribute.IsDefined(prop, typeof(ForeignKeyAttribute)))
                    continue;
                switch (type)
                {
                    case Type intType when intType == typeof(int) || intType == typeof(int?):
                        {
                            helper = helper.MapInteger("\"" + prop.Name + "\"",  x => (int?)typeof(T).GetProperty(prop.Name).GetValue(x, null));
                            break;
                        }
                    case Type stringType when stringType == typeof(string):
                        {
                            helper = helper.MapText("\"" + prop.Name + "\"", x => (string)typeof(T).GetProperty(prop.Name).GetValue(x, null));
                            break;
                        }
                    case Type dateType when dateType == typeof(DateTime) || dateType == typeof(DateTime?):
                        {
                            helper = helper.MapTimeStamp("\"" + prop.Name + "\"", x => (DateTime?)typeof(T).GetProperty(prop.Name).GetValue(x, null));
                            break;
                        }
                    case Type decimalType when decimalType == typeof(decimal) || decimalType == typeof(decimal?):
                        {
                            helper = helper.MapMoney("\"" + prop.Name + "\"", x => (decimal?)typeof(T).GetProperty(prop.Name).GetValue(x, null));
                            break;
                        }
                    case Type doubleType when doubleType == typeof(double) || doubleType == typeof(double?):
                        {
                            helper = helper.MapDouble("\"" + prop.Name + "\"", x => (double?)typeof(T).GetProperty(prop.Name).GetValue(x, null));
                            break;
                        }
                    case Type floatType when floatType == typeof(float) || floatType == typeof(float?):
                        {
                            helper = helper.MapReal("\"" + prop.Name + "\"", x => (float?)typeof(T).GetProperty(prop.Name).GetValue(x, null));
                            break;
                        }
                    case Type guidType when guidType == typeof(Guid):
                        {
                            helper = helper.MapUUID("\"" + prop.Name + "\"", x => (Guid)typeof(T).GetProperty(prop.Name).GetValue(x, null));
                            break;
                        }
                }
            }
            return helper;
        }

私はそれを次のように使用しています(Undertakeという名前のエンティティがありました)。

var undertakingHelper = BulkMapper.CreateHelper<Model.Undertaking>("dbo", nameof(Model.Undertaking));
undertakingHelper.SaveAll(transaction.UnderlyingTransaction.Connection as Npgsql.NpgsqlConnection, undertakingsToAdd));

トランザクションの例を示しましたが、コンテキストから取得した通常の接続でも実行できます。undertakeingsToAddは、通常のエンティティレコードを列挙できます。これをDBにbulkInsertします。

この解決策は、数時間の研究と試行の結果得られたものであり、はるかに速く、最終的に使いやすく、無料であると期待できるものです。上記の理由だけでなく、Postgresql自体で問題がなかった唯一のソリューションであるため、このソリューションを使用することをお勧めします。他の多くのソリューションは、SqlServerなどで問題なく動作します。


0

秘密は、同じ空のステージングテーブルに挿入することです。インサートは素早く軽量化されています。次に、シングルを実行しますそこからメインの大きなテーブルに挿入をします。次に、次のバッチに備えてステージングテーブルを切り捨てます。

すなわち。

insert into some_staging_table using Entity Framework.

-- Single insert into main table (this could be a tiny stored proc call)
insert into some_main_already_large_table (columns...)
   select (columns...) from some_staging_table
truncate table some_staging_table

EFを使用して、すべてのレコードを空のステージングテーブルに追加します。次に、SQLを使用して、単一の SQL命令でメイン(大規模で低速)テーブルに挿入します。次に、ステージングテーブルを空にします。これは、すでに大きなテーブルに大量のデータを挿入する非常に高速な方法です。
Simon Hughes

13
EFを使用する場合、ステージングテーブルにレコードを追加しますが、実際にEFでこれを試しましたか?EFは挿入ごとにデータベースへの個別の呼び出しを発行するため、OPが回避しようとしているのと同じパフォーマンスヒットが表示されると思います。ステージングテーブルはこの問題をどのように回避しますか?
ジムウーリー2013

-1

ただし、(+ 4000)を超える挿入の場合は、ストアドプロシージャを使用することをお勧めします。経過時間を添付。20インチで11.788行挿入しましたここに画像の説明を入力してください

それはコードです

 public void InsertDataBase(MyEntity entity)
    {
        repository.Database.ExecuteSqlCommand("sp_mystored " +
                "@param1, @param2"
                 new SqlParameter("@param1", entity.property1),
                 new SqlParameter("@param2", entity.property2));
    }

-1

データを挿入するには、xml形式の入力データを受け取るストアドプロシージャを使用します。

c#コードからデータをxmlとして挿入します。

たとえば、c#では、構文は次のようになります。

object id_application = db.ExecuteScalar("procSaveApplication", xml)

-7

この手法を使用して、Entity Frameworkにレコードを挿入する速度を向上させます。ここでは、単純なストアドプロシージャを使用してレコードを挿入しています。このストアドプロシージャを実行するには、Raw SQLを実行するEntity Frameworkの.FromSql()メソッドを使用します。

ストアドプロシージャコード:

CREATE PROCEDURE TestProc
@FirstParam VARCHAR(50),
@SecondParam VARCHAR(50)

AS
  Insert into SomeTable(Name, Address) values(@FirstParam, @SecondParam) 
GO

次に、すべての4000レコードをループします。を、格納されているを実行するEntity Frameworkコードを追加し

プロシージャは100回のループごとに1回です。

このために、このプロシージャを実行する文字列クエリを作成し、レコードのすべてのセットに追加し続けます。

次に、ループが100の倍数で実行されていることを確認し、その場合はを使用して実行し.FromSql()ます。

したがって、4000レコードの場合、手順を実行する必要があるのは4000/100 = 40回だけ です

以下のコードを確認してください:

string execQuery = "";
var context = new MyContext();
for (int i = 0; i < 4000; i++)
{
    execQuery += "EXEC TestProc @FirstParam = 'First'" + i + "'', @SecondParam = 'Second'" + i + "''";

    if (i % 100 == 0)
    {
        context.Student.FromSql(execQuery);
        execQuery = "";
    }
}

これは効率的かもしれませんが、エンティティフレームワークを使用しないことと同等です。OPの質問は、Entity Frameworkのコンテキストで効率を最大化する方法
でした
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.