Entity Frameworkの更新および削除操作の高速化

ORM Entity Frameworkにはアキレス腱があります。 これは、CRUD操作から、作成と読み取りのみが最適に実行されるという事実から成ります。 [ボックス内の更新と削除]オプションでは、最初にデータベースからレコード全体を読み取る必要があり、その後でのみ更新できます。 そして、はい、レコードを削除するには、最初にそれも読む必要があります。

つまり 残念なプログラマーは
using (var ctx = new ShopEntities()) { foreach (var u in ctx.Users) { ctx.Users.Remove(u); } ctx.SaveChanges(); } 


しかし、EntityFramework.Extendedパッケージのリリースにより、状況は根本的に変化しています。

そのため、コマンド「Install-Package EntityFramework.Extended」を使用して、リポジトリからパッケージをインストールします。 次に、名前空間「EntityFramework.Extensions」を接続します。
そして魔法が始まります。

削除は次のようになります。
 using (var ctx = new ShopEntities()) { var itemsDeleted = ctx.Users.Delete(u => u.Orders.Count > 10); //,      //,     ctx.SaveChanges(),    Console.WriteLine("{0} users were deleted", itemsDeleted); } 


ちなみに、サーバーに飛んだものを見るのは不必要ではありません。
そんな依頼でした
 DELETE [dbo].[Users] FROM [dbo].[Users] AS j0 INNER JOIN ( SELECT [Project1].[ID] AS [ID] FROM ( SELECT [Extent1].[ID] AS [ID], (SELECT COUNT(1) AS [A1] FROM [dbo].[Orders] AS [Extent2] WHERE [Extent1].[ID] = [Extent2].[UserID]) AS [C1] FROM [dbo].[Users] AS [Extent1] ) AS [Project1] WHERE [Project1].[C1] > 10 ) AS j1 ON (j0.[ID] = j1.[ID]) go 


ご覧のとおり、これは条件付きの正直な(不器用ではありますが)グループ削除要求です。

同様に、レコードを更新します。 更新する前に、データベースからデータを読み取る必要がなくなりました。 同時に、レコード内の既存のデータを使用でき、定数のみに制限されません。
 using (var ctx = new ShopEntities()) { var itemsUpdated = ctx.Users.Where(u => u.Orders.Count > 0).Update(u => new User { BonusCount = u.BonusCount + 1 }); //,      //,     ctx.SaveChanges(),    Console.WriteLine("{0} users were updated", itemsUpdated); } 


プロファイラーでSQLクエリを確認します。
 UPDATE [dbo].[Users] SET [BonusCount] = [BonusCount] + 1 FROM [dbo].[Users] AS j0 INNER JOIN ( SELECT [Project1].[ID] AS [ID] FROM ( SELECT [Extent1].[ID] AS [ID], (SELECT COUNT(1) AS [A1] FROM [dbo].[Orders] AS [Extent2] WHERE [Extent1].[ID] = [Extent2].[UserID]) AS [C1] FROM [dbo].[Users] AS [Extent1] ) AS [Project1] WHERE [Project1].[C1] > 0 ) AS j1 ON (j0.[ID] = j1.[ID]) go 


これらは、この拡張パッケージをインストールする価値があるため、2つの主要な機能です。
しかし、砂糖もあります。 パッケージの作成者は、サンプリングのリクエストを蓄積してから、それらを1つのアプローチで実行するように提案しています。 これを行うには、具体化する前にデータをFuture()としてマークし、オブジェクトのいずれかを具体化すると、残りは自動的に具体化されます。
 using (var ctx = new ShopEntities()) { var alexUsers = ctx.Users.Where(u => u.Name == "Alex").Future(); var usersWithOrders = ctx.Users.Where(c => c.Orders.Any()).Future(); foreach (var item in alexUsers) //          round-trip  . { Console.WriteLine("{0} {1}", item.ID, item.Name); } foreach (var item in usersWithOrders) //   SQL { Console.WriteLine("{0} {1}", item.ID, item.Name); } } 


しかし、それはSQLクエリでした
 -- Query #1 SELECT [Extent1].[ID] AS [ID], [Extent1].[Name] AS [Name], [Extent1].[IsTop10] AS [IsTop10], [Extent1].[BonusCount] AS [BonusCount] FROM [dbo].[Users] AS [Extent1] WHERE (N'Alex' = [Extent1].[Name]) AND ([Extent1].[Name] IS NOT NULL); -- Query #2 SELECT [Extent1].[ID] AS [ID], [Extent1].[Name] AS [Name], [Extent1].[IsTop10] AS [IsTop10], [Extent1].[BonusCount] AS [BonusCount] FROM [dbo].[Users] AS [Extent1] WHERE EXISTS (SELECT 1 AS [C1] FROM [dbo].[Orders] AS [Extent2] WHERE [Extent1].[ID] = [Extent2].[UserID] ); go 


Future拡張機能に加えて、FutureCount、FutureFirstOrDefault、FutureValueも使用できます。

しかし、それだけではありません。 まれにしか変更されないデータに対する頻繁なリクエストを処理するモジュールがあると想像してください。 たとえば、ユーザー認証。 結果をキャッシュしますか? お願いします。 コードからわかるように、キャッシュはコンテキストによって制限されませんが、再作成後も関連性を保ちます。

 for (int i = 0; i < 2; i++) { using (var ctx = new ShopEntities()) { var alexUsers = ctx.Users.Where(u => u.Name == "Alex").FromCache(); foreach (var item in alexUsers) //i == 0    , i == 1      { Console.WriteLine("{0} {1}", item.ID, item.Name); } } } 


FromCacheメソッドには、キャッシュ時間を指定するためのオーバーロードがあります。

したがって、EntityFramework.Extendedのインストールと使用は、EntityFrameworkの子供時代の病気を排除するだけでなく、ストアドプロシージャの下位レベルに行かずに高負荷の場所でそれを加速させます。

Source: https://habr.com/ru/post/J203214/


All Articles