Linq Toエンティティvs. グループ化の例としてのオブジェクトへのLinq

LINQは快適で美しいですが、同時にかなり陰湿な抽象化です。 通常、最も予期しないことは、LINQとLINQ To Objectsの実装の一部で発生します。 今日は、1つの例を挙げて、LINQ To Entities(Entity Framework)とLINQ To Objectsのコラボレーションを見ていきます。







リポジトリメソッドを入力として使用します。これは、顧客識別子のリストを入力として受け取り、これらの識別子でグループ化された一連の注文を返します(Ordersテーブルには、OrderId、OrderDate、およびCustomerIdのフィールドが含まれます)。

public IDictionary<long, List<Order>> GetOrdersByCustomersIds(IList<long> customersIds) { using (var ctx = new RepositoryContext()) { return ctx.Orders. Where(o => customersIds.Contains(o.Id)). GroupBy(o => o.CustomerId). ToDictionary(o => o.Key, o => o.ToList()); } }
      
      







ちょっと待って! どのように機能しますか? 実際、GROUP BYクエリを実行するときは、グループ化が行われるフィールドと集計値のみを選択できます。 この問題の標準的な解決策は、テーブルデータとグループ化結果の結合です。 このようなもの:

  SELECT o1.*, MinTotal FROM Orders as o1 INNER JOIN (SELECT o2.CustomerId, Min(o2.Total) as MinTotal FROM Orders o2 GROUP BY o2.CustomerId) as o3 ON o1.CustomerId = o3.CustomerId Where o1.CustomerId in (1, 2, 3, 4, 5)
      
      





そのようなものは、EFプロバイダーによって生成される必要があります。 それを確認しましょう。 MySQL .NETコネクタ(MySQLの公式ADO.NETプロバイダー)が手元にあったので、それを利用して、次の生成された要求を受け取りました(入力として1から5の識別子のリストを渡します)。

 SELECT `Project2`.`C1`, `Project2`.`CustomerId`, `Project2`.`C2`, `Project2`.`CustomerId1`, `Project2`.`Id`, `Project2`.`OrderDate` FROM (SELECT `Distinct1`.`CustomerId`, 1 AS `C1`, `Extent2`.`CustomerId` AS `CustomerId1`, `Extent2`.`Id`, `Extent2`.`OrderDate`, CASE WHEN (`Extent2`.`CustomerId` IS NULL) THEN (NULL) ELSE (1) END AS `C2` FROM (SELECT DISTINCT `Extent1`.`CustomerId` FROM `orders` AS `Extent1` WHERE ((1 = `Extent1`.`Id`) OR (2 = `Extent1`.`Id`)) OR (((3 = `Extent1`.`Id`) OR (4 = `Extent1`.`Id`)) OR (5 = `Extent1`.`Id`))) AS `Distinct1` LEFT OUTER JOIN `orders` AS `Extent2` ON (((1 = `Extent2`.`Id`) OR (2 = `Extent2`.`Id`)) OR (((3 = `Extent2`.`Id`) OR (4 = `Extent2`.`Id`)) OR (5 = `Extent2`.`Id`))) AND (`Distinct1`.`CustomerId` = `Extent2`.`CustomerId`)) AS `Project2` ORDER BY `CustomerId` ASC, `C2` ASC
      
      





手動での実装はわずかに悪いですが、全体として、上記の発言はたどることができます。



やめて! データベースレベルでグループ化を使用する理由 集約関数が使用される場合、グループ化は正当化されます(上記のクエリの手動実装のように)。 この場合、グループ化は、取得したデータの便利な表現にすぎません。 リポジトリのメソッドを少し変更し、グループ化プロセスをLINQ To Objectsレベルに移行しましょう。

 public IDictionary<long, List<Order>> GetOrdersByCustomersIds(IList<long> customersIds) { using (var ctx = new RepositoryContext()) { return ctx.Orders. Where(o => customersIds.Contains(o.Id)). AsEnumerable(). GroupBy(o => o.CustomerId). ToDictionary(o => o.Key, o => o.ToList()); } }
      
      





図を完成させるために、EFプロバイダーが生成するリクエストを見てみましょう。

 SELECT `Extent1`.`CustomerId`, `Extent1`.`Id`, `Extent1`.`OrderDate` FROM `orders` AS `Extent1` WHERE ((1 = `Extent1`.`Id`) OR (2 = `Extent1`.`Id`)) OR (((3 = `Extent1`.`Id`) OR (4 = `Extent1`.`Id`)) OR (5 = `Extent1`.`Id`))
      
      





間違いなく、このクエリは前のクエリよりも効率的です。



実際、それがすべてです。 特別なことは何もありません-彼自身がこのtrapに陥った後、LINQ To XからLINQ To Objectsへの陰湿な移行に注意を喚起したかっただけです。 警戒してください!



PS MySQL .NET Connectorを使用したという事実にもかかわらず、私は本番環境でこのプロバイダーを使用することを断固として推奨ません 。これはプロバイダーではなく、長年にわたって続かない集中的なバグです。



All Articles