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の子供時代の病気を排除するだけでなく、ストアドプロシージャの下位レベルに行かずに高負荷の場所でそれを加速させます。



All Articles