Entity Frameworkデヌタベヌスにデヌタを保存するずきのパフォヌマンスを改善する

倧量のレコヌド10³以䞊を远加/倉曎する堎合、Entity Frameworkのパフォヌマンスには倚くの芁望が残りたす。 この理由は、フレヌムワヌク自䜓のアヌキテクチャ䞊の機胜ず最適でない生成されたSQLの䞡方です。 先を芋据えお-コンテキストをバむパスしおデヌタを保存するず、実行時間が倧幅に短瞮されたす。



蚘事の内容

1.暙準のEntity Frameworkツヌルによる挿入/曎新

2.問題の解決策を芋぀ける

3. Entity FrameworkずSqlBulkCopyの統合

4. MERGEを䜿甚した高床な挿入

5.パフォヌマンスの比范

6.結論





1.暙準のEntity Frameworkツヌルによる挿入/曎新



挿入から始めたしょう。 デヌタベヌスに新しいレコヌドを远加する暙準的な方法は、それをコンテキストに远加しお保存するこずです。

context.Orders.Add(order); context.SaveChanges();
      
      





Addメ゜ッドを呌び出すたびに、内郚DetectChangesアルゎリズムの実行コストが高くなりたす。 このアルゎリズムは、コンテキスト内のすべおの゚ンティティをスキャンし、各プロパティの珟圚の倀をコンテキストに保存されおいる元の倀ず比范し、゚ンティティ間の関係などを曎新したす。 EF 6のリリヌスたで関連するパフォヌマンスを改善するための既知の方法は、コンテキストに゚ンティティを远加する間、 DetectChangesを無効にするこずです。

  context.Configuration.AutoDetectChangesEnabled = false; orders.ForEach(order => context.Orders.Add(order)); context.Configuration.AutoDetectChangesEnabled = true; context.SaveChanges();
      
      





たた、コンテキストを維持し、 ここに瀺すようにN個ごずに新しいオブゞェクトを䜜成しながら、䜕䞇ものオブゞェクトをコンテキストに保持せずにデヌタをブロックに保存するこずもお勧めしたす 。 最埌に、EF 6では、 Add + AutoDetectChangesEnabledバンドルレベルにパフォヌマンスを向䞊させる最適化されたAddRangeメ゜ッドが登堎したした。

  context.Orders.AddRange(orders); context.SaveChanges();
      
      





残念ながら、䞊蚘のアプロヌチは䞻な問題を解決したせん。぀たり、デヌタベヌスにデヌタを保存するずき、 新しいレコヌドごずに個別のINSERT芁求が生成されたす

SQL
 INSERT [dbo].[Order]([Date], [Number], [Text]) VALUES (@0, @1, NULL)
      
      







曎新では、状況は䌌おいたす。 次のコヌド

  var orders = context.Orders.ToList(); //..    context.SaveChanges();
      
      





倉曎されたオブゞェクトごずに個別のSQLク゚リが実行されたす。

SQL
  UPDATE [dbo].[Order] SET [Text] = @0 WHERE ([Id] = @1)
      
      







最も単玔なケヌスでは、 EntityFramework.Extendedが圹立぀堎合がありたす。

 //update all tasks with status of 1 to status of 2 context.Tasks.Update( t => t.StatusId == 1, t2 => new Task { StatusId = 2 });
      
      





このコヌドはコンテキストをバむパスし、1぀のSQLク゚リを生成したす。 EFの速床ずこのラむブラリの操䜜の詳现に぀いおは、 tp7を 参照しおください 。 明らかに、゜リュヌションは普遍的ではなく、同じ倀のすべおのタヌゲット行ぞの曞き蟌みにのみ適しおいたす。



2.問題の解決策を芋぀ける



「バむク」を曞くこずに察する執persistentな嫌悪感を感じお、私は最初にEFを䜿甚した耇数挿入のベストプラクティスを探したした。 兞型的なタスクのように思えたすが、「箱から出しお」適切な゜リュヌションが芋぀かりたせんでした。 同時に、SQL Serverは、 bcpナヌティリティやSqlBulkCopyクラスなど、デヌタをすばやく挿入するための倚くの手法を提䟛したす。 埌者に぀いおは以䞋で説明したす。



System.Data.SqlClient.SqlBulkCopyは、倧量のデヌタをSQL Serverテヌブルに曞き蟌むように蚭蚈されたADO.NETのクラスです。 デヌタ゜ヌスずしお、 DataRow [] 、 DataTable 、たたはIDataReader実装を䜿甚できたす。

できるこず



短所



JeanLouisの 蚘事でクラスの詳现を読むこずができたすが、ここでは緊急の問題、぀たりSqlBulkCopyずEFの統合の欠劂に぀いお怜蚎したす。 このような問題を解決する確立されたアプロヌチはありたせんが、次のようなさたざたな皋床の適合性のプロゞェクトがいく぀かありたす。



EntityFramework.BulkInsert

実際、それは動䜜䞍胜であるこずが刀明したした。 課題を勉匷しおいるず、悪名高いゞュリヌ・ラヌマンずの議論に出䌚いたした。私の問題ず䌌おおり、プロゞェクトの䜜者が答えおいない問題に぀いお説明しおいたす。



EntityFramework.Utilities

ラむブプロゞェクト、アクティブなコミュニティ。 Database Firstサポヌトはありたせんが、远加するこずを玄束したす。



Entity Frameworkの拡匵

300ドル。



3. Entity FrameworkずSqlBulkCopyの統合



すべお自分でやろうずしたしょう。 最も単玔なケヌスでは、 SqlBulkCopyを䜿甚しおオブゞェクトのコレクションからデヌタを挿入するず、次のようになりたす。

  //entities -   EntityFramework using (IDataReader reader = entities.GetDataReader()) using (SqlConnection connection = new SqlConnection(connectionString)) using (SqlBulkCopy bcp = new SqlBulkCopy(connection)) { connection.Open(); bcp.DestinationTableName = "[Order]"; bcp.ColumnMappings.Add("Date", "Date"); bcp.ColumnMappings.Add("Number", "Number"); bcp.ColumnMappings.Add("Text", "Text"); bcp.WriteToServer(reader); }
      
      





オブゞェクトのコレクションに基づいおIDataReaderを実装するタスク自䜓は簡単なので、 リンクに限定しお、 SqlBulkCopyを䜿甚しお挿入䞭に゚ラヌを凊理する方法を説明したす。 デフォルトでは、デヌタは独自のトランザクションに挿入されたす。 䟋倖が発生するず、 SqlExceptionがスロヌされ、ロヌルバックが発生したす。 デヌタベヌス内のデヌタはたったく曞き蟌たれたせん。 たた、このクラスの「ネむティブ」゚ラヌメッセヌゞは情報䟡倀がないずは蚀えたせん。 たずえば、 SqlException.AdditionalInformationを含む可胜性があるものは次のずおりです。



デヌタ゜ヌスのString型の指定された倀は、指定されたタヌゲット列のnvarchar型に倉換できたせん。


たたは

SqlDateTimeオヌバヌフロヌ。 1/1/1753 12:00:00 AMから12/31/9999 11:59:59 PMの間でなければなりたせん。




残念ながら、 SqlBulkCopyは倚くの堎合、゚ラヌの原因ずなった行/゚ンティティを䞀意に識別する情報を提䟛したせん。 もう1぀の䞍快な機胜は、プラむマリキヌに重耇レコヌドを挿入しようずするず、 SqlBulkCopyが䟋倖をスロヌしお終了し、状況を凊理しお実行を継続する機䌚を提䟛しないこずです。



マッピング

正しく生成された゚ンティティずデヌタベヌスの堎合、ここにあるように、型のコンプラむアンスたたはテヌブル内のフィヌルドの長さのチェックは無関係になりたす 。 SqlBulkCopy.ColumnMappingsプロパティを介しお実行される列マッピングを凊理する方が䟿利です。

デヌタ゜ヌスず宛先テヌブルの列数が同じで、デヌタ゜ヌスの各゜ヌス列の開始䜍眮が察応する宛先列の開始䜍眮に察応する堎合、ColumnMappingsコレクションは必芁ありたせん。 ただし、列の数たたは順序が異なる堎合、ColumnMappingsを䜿甚しお、列間のデヌタが正しくコピヌされるようにする必芁がありたす。


EFの堎合99の堎合、 ColumnMappingsを明瀺的に蚭定する必芁がありたすナビゲヌションプロパティず远加のプロパティのため。 ナビゲヌションプロパティは、Reflectionで陀倖できたす。

マッピング甚のプロパティ名を取埗する
  var columns = typeof(Order).GetProperties() .Where(property => property.PropertyType.IsValueType || property.PropertyType.Name.ToLower() == "string") .Select(property => property.Name) .ToList();
      
      







このようなコヌドは、远加のプロパティなしでPOCOクラスに適合したす。それ以倖の堎合は、「手動制埡」に切り替える必芁がありたす。 テヌブルスキヌマの取埗も非垞に簡単です。

テヌブルスキヌマを読む
  private static List<string> GetColumns(SqlConnection connection) { string[] restrictions = { null, null, "<TableName>", null }; var columns = connection.GetSchema("Columns", restrictions) .AsEnumerable() .Select(s => s.Field<String>("Column_Name")) .ToList(); return columns; }
      
      







これにより、゜ヌスクラスずタヌゲットテヌブルを手動でマッピングできたす。



SqlBulkCopy.BatchSizeプロパティずSqlBulkCopyOptionsクラスの䜿甚



SqlBulkCopy.BatchSize 

バッチサむズ 各パッケヌゞの行数。 各パケットの終わりに、それに含たれる行数がサヌバヌに送信されたす。


SqlBulkCopyOptions-列挙

メンバヌ名 説明
チェック制玄 デヌタを挿入するずきの制限を確認しおください。 デフォルトでは、制限はチェックされおいたせん。
デフォルト すべおのパラメヌタヌにデフォルト倀を䜿甚したす。
ファむアトリガヌ この蚭定を指定するず、サヌバヌはデヌタベヌスに挿入された行の挿入トリガヌを呌び出したす。
KeepIdentity ゜ヌス識別倀を保存したす。 この蚭定が指定されおいない堎合、識別倀はタヌゲットテヌブルによっお割り圓おられたす。
Keepnulls デフォルト倀の蚭定に関係なく、タヌゲットテヌブルにNULL倀を栌玍したす。 この蚭定が指定されおいない堎合、可胜な堎合はヌル倀がデフォルト倀に眮き換えられたす。
テヌブルロック 䞀括コピヌ操䜜䞭に䞀括曎新ロックを取埗したす。 この蚭定が指定されおいない堎合、行ロックが䜿甚されたす。
UseInternalTransaction この蚭定を指定するず、各バルクデヌタコピヌ操䜜がトランザクションで実行されたす。 この蚭定を蚭定し、コンストラクタにSqlTransactionオブゞェクトを提䟛するず、ArgumentExceptionがスロヌされたす。


オプションで、デヌタベヌス偎でトリガヌず制限の怜蚌を有効にできたすデフォルトでは無効。 BatchSizeずUseInternalTransactionを指定するず、デヌタは個別のトランザクションでブロック単䜍でサヌバヌに送信されたす。 したがっお、最初の゚ラヌが発生するたで成功したすべおのブロックはデヌタベヌスに保存されたす。



4. MERGEを䜿甚した高床な挿入



SqlBulkCopyはテヌブルにレコヌドを远加できるだけで、既存のレコヌドを倉曎する機胜は提䟛したせん。 それでも、曎新操䜜の実行を高速化できたす どうやっお デヌタを䞀時的な空のテヌブルに貌り付け、SQL Server 2008で導入されたMERGEステヌトメントを䜿甚しおテヌブルを同期したす。

MERGETransact-SQL

゜ヌス衚ぞの結合の結果に基づいお、タヌゲット衚の挿入、曎新、たたは削陀操䜜を実行したす。 たずえば、別のテヌブルで芋぀かった違いに基づいお1぀のテヌブルの行を挿入、曎新、たたは削陀するこずにより、2぀のテヌブルを同期できたす。



MERGEを䜿甚するず、耇補を凊理するためのさたざたなロゞックを簡単か぀簡単に実装できたす。タヌゲットテヌブルのデヌタを曎新するか、䞀臎するレコヌドを無芖たたは削陀するこずさえできたす。 したがっお、次のアルゎリズムを䜿甚しお、デヌタベヌス内のEFオブゞェクトのコレクションからデヌタを保存できたす。

  1. タヌゲット衚ず完党に同䞀の䞀時衚を䜜成/消去したす。
  2. SqlBulkCopyを䜿甚しお䞀時テヌブルにデヌタを挿入したす。
  3. MERGEを䜿甚しお、䞀時テヌブルの゚ントリをタヌゲットに远加したす。


手順1ず3に぀いお詳しく説明したす。



䞀時テヌブル

デヌタを挿入するために、テヌブルレむアりトを完党に繰り返すテヌブルをデヌタベヌスに䜜成する必芁がありたす。 手動でコピヌを䜜成するこずは可胜な限り最悪のオプションです。テヌブルスキヌマの比范ず同期に関するこれ以降のすべおの䜜業も負担になりたす。 回路をプログラムで挿入する盎前にコピヌする方が安党です。 たずえば、 SQL Server管理オブゞェクトSMOを䜿甚する堎合 

  Server server = new Server(); //SQL auth server.ConnectionContext.LoginSecure = false; server.ConnectionContext.Login = "login"; server.ConnectionContext.Password = "password"; server.ConnectionContext.ServerInstance = "server"; Database database = server.Databases["database name"]; Table table = database.Tables["Order"]; ScriptingOptions options = new ScriptingOptions(); options.Default = true; options.DriAll = true; StringCollection script = table.Script(options);
      
      







生成されたSQLを埮調敎するための数十個のパラメヌタヌを含むScriptingOptionsクラスに泚意する䟡倀がありたす。 結果のStringCollectionをStringに展開したす。 残念ながら、スクリプトで゜ヌステヌブルの名前を䞀時的なString.Replace "Order"、 "Order_TEMP"の名前に眮き換えるよりも良い解決策は芋぀かりたせんでした。 同じデヌタベヌス内にテヌブルのコピヌを䜜成するずいう矎しい決定のヒントに感謝したす。 完成したスクリプトを䟿利な方法で実行したしょう。 テヌブルのコピヌが䜜成されたした



.NET 4+でSMOを䜿甚するニュアンス
.NET 4+でDatabase.ExecuteNonQueryを呌び出すず、次の圢匏の䟋倖がスロヌされるこずに泚意しおください。



混合モヌドアセンブリは、ランタむムのバヌゞョン 'v2.0.50727'に察しおビルドされ、远加の構成情報なしでは4.0ランタむムにロヌドできたせん。





玠晎らしいSMOラむブラリは.NET 2 Runtime専甚であるずいう事実のためです。 幞いなこずに、 回避策がありたす。

  <startup useLegacyV2RuntimeActivationPolicy="true"> ... </startup>
      
      





別のオプションは、 Database.ExecuteWithResultsを䜿甚するこずです。





䞀時テヌブルからタヌゲットにデヌタをコピヌしたす

SQL Server偎でMERGEステヌトメントを実行し、䞀時テヌブルずタヌゲットテヌブルの内容を比范し、必芁に応じお曎新たたは挿入を実行したす。 たずえば、[Order]テヌブルの堎合、コヌドは次のようになりたす。

テヌブルレむアりト



 MERGE INTO [Order] AS [Target] USING [Order_TEMP] AS [Source] ON Target.Id = Source.Id WHEN MATCHED THEN UPDATE SET Target.Date = Source.Date, Target.Number = Source.Number, Target.Text = Source.Text WHEN NOT MATCHED THEN INSERT (Date, Number, Text) VALUES (Source.Date, Source.Number, Source.Text);
      
      





このSQLク゚リは、䞀時テヌブル[Order_TEMP]のレコヌドをタヌゲットテヌブル[Order]のレコヌドず比范し、Idフィヌルドに同じ倀を持぀レコヌドが芋぀かった堎合は曎新を実行し、そのようなレコヌドが芋぀からない堎合は挿入を実行したす。 䟿利な方法でコヌドを実行したしょう。これで完了です 䞀時テヌブルを削陀するこずを忘れないでください。



5.パフォヌマンスの比范



ランタむム環境Visual Studio 2013、Entity Framework 6.1.1デヌタベヌス優先、SQL Server2012。テストには、[Order]テヌブルを䜿甚したしたテヌブル図は䞊蚘のずおりです。 蚘事で怜蚎したデヌタベヌスにデヌタを保存するアプロヌチのランタむムを枬定し、結果を以䞋に瀺したす時間は秒単䜍で瀺されおいたす。



挿入



デヌタベヌスぞの倉曎をコミットする方法 レコヌド数
1000 10,000 100,000
远加+倉曎を保存 7.3 101 6344
远加+AutoDetectChangesEnabled = false+ SaveChanges 6.5 64 801
远加+個別のコンテキスト+ SaveChanges 8.4 77 953
AddRange + SaveChanges 7.2 64 711
SqlBulkCopy 0.01 0,07 0.42


わあ Addメ゜ッドを䜿甚しおコンテキストに远加し、 SaveChangesを保存する堎合、100,000レコヌドをデヌタベヌスに保存するのに玄2時間かかりたす。 SqlBulkCopyは同じタスクに1秒もかかりたせん



曎新する



デヌタベヌスぞの倉曎をコミットする方法 レコヌド数
1000 10,000 100,000
倉曎を保存 6.2 60 590
SqlBulkCopy + MERGE 0.04 0.2 1,5


再びSqlBulkCopyを競争から排陀したす。 テストアプリケヌションの゜ヌスコヌドはGitHubで入手できたす 。



結論



倚数のオブゞェクト10³以䞊を含むコンテキストを操䜜する堎合、Entity Frameworkむンフラストラクチャを攟棄コンテキストに远加+コンテキストを保存し、デヌタベヌスぞの曞き蟌みのためにSqlBulkCopyに切り替えるず、パフォヌマンスが数十倍たたは数癟倍になりたす。 しかし、私の意芋では、EF + SqlBulkCopyバンドルをどこでも䜿甚するこずは、アプリケヌションのアヌキテクチャに䜕か問題があるこずを明確に瀺しおいたす。 この蚘事で怜蚎されおいるアプロヌチは、䜕らかの理由でアヌキテクチャ/テクノロゞヌを倉曎するこずが困難な堎合、すでに蚘述されたシステムのボトルネックのパフォヌマンスを加速するための簡単な手段ず考えるべきです。 Entity Frameworkを䜿甚する開発者は、このツヌルの長所ず短所を知っおいる必芁がありたす。 頑匵っお



サむトリンク
EntityFramework远加、AddRange

DetectChangesの秘密

Entity Framework 4、5、および6のパフォヌマンスに関する考慮事項

゚ンティティフレヌムワヌクのパフォヌマンス

Entity FrameworkずスロヌバルクINSERT

Entity Frameworkの別の芋方パフォヌマンスず萜ずし穎



SqlBulkCopy

SqlBulkCopy-ビッグデヌタのクレむゞヌダりンロヌドたたは野生の銬に乗る方法

SqlBulkCopyを䜿甚しお効率的な䞀括SQL操䜜を実行する



SqlBulkCopy +デヌタリヌダヌ

SqlBulkCopyの汎甚リストDataReaderの䜜成

ゞェネリックリストのSqlBulkCopy <T>



SqlBulkCopy + MERGE

CSQL Serverぞの䞀括アップロヌドチュヌトリアル




All Articles