GroupByメソッド
GroupByメソッドは、SQLのGROUP BY句に対応し、同じ条件を持つレコードをグループ化する機能です。リスト9は商品(Product)を担当者(Employee)ごとにグループ分けして取得するサンプルです。
//GroupByメソッドで、担当者ごとにグルーピングする //戻り値はIGrouping<Employee, Product>のコレクション(IQueryable) var groupByList = context.Products.GroupBy(x => x.Employee); //(1)外側のコレクションを回してグループを取得 foreach (var group in groupByList) { //(2)担当者(Employee)はIGroupingのKeyプロパティ Console.WriteLine("Employee Name: {0}", group.Key.Name); //(3)グループ内のコレクションを回して商品を取得 foreach (var product in group) { //NameとPriceを取り出す Console.WriteLine(" Name: {0}, Price: {1}", product.Name, product.Price); } } ↓実行結果 Employee Name: 土井 Name: ショートケーキ, Price: 250 Name: バームクーヘン, Price: 500 Name: 季節のアイスクリーム, Price: 220 Employee Name: 佐藤 Name: おいしい牛乳, Price: 250 Name: 流行のヨーグルト, Price: 320
GroupByメソッドのラムダ式では、グルーピングするための条件となるプロパティを指定します。今回はEmployee(担当者)プロパティでグルーピングしています。GroupByメソッドの戻り値は少々分かりづらい構造となっていますので図で解説します(図3)。
GroupByメソッドの戻り値は「IQueryable<IGrouping<Employee, Product>>」です。何かの呪文と見まがうような複雑さでゲンナリしますが、「グループを表すIGrouping<Employee, Product>のコレクションだ」と理解してください。そしてグループを表すIGrouping<Employee, Product>はグルーピング条件となったEmployeeをプロパティに持ち、さらにProductのコレクションとなっていますので、コレクションが2段重ねの構成となっています。
そのため、リスト9では、まず(1)で外側のコレクションであるIQueryableから1件ずつグループを取得し、さらに(3)で内側のコレクションであるIGrouping<Employee, Product>から1件ずつ商品を取り出しています。また、グルーピング条件となったEmployeeは、(2)のようにIGrouping<Employee, Product>のKeyプロパティに格納されています。
GroupByメソッドの戻り値はやや分かりづらい構造ですが、「同じグループのものがコレクション(IGrouping<Employee, Product>)にまとまり、さらにそのグループがコレクション(IQueryable)としてまとまっている」と考えれば理解しやすいでしょう。
統合言語クエリであるLINQは、クエリ自体がコンパイル可能な言語要素であるため、Visual Studioの強力なコーディング支援機能を活用できます。これまでも幾らか強調してきましたが、以下のような点が大きなメリットと言えるでしょう。
- ラムダ式内でフィールド名がIntelliSenseで出てくるので、フィールド名のタイプミスが起きない
- フィールドのデータ型も認識されているので、フィールド定義が数値型だったか文字列型だったかなどで悩む必要がない
さらに、統合言語クエリであることによって、もっと複雑な要望にも容易に対応が可能です。たとえば、「ProductsテーブルのNameフィールドの名前をProductNameに変更したい。ただしNameというフィールド名は他のテーブルでも多用しているので、単純にNameをProductNameに文字列置換しては問題が起きる」といった要件を考えましょう。
通常であれば、文字列置換ができないため、ProductsテーブルのNameフィールドを参照している箇所を一つずつ見つけながら、ProductNameに置換していくことになるでしょうか。Productsテーブルが様々な箇所で使用されているテーブルだとしたら……気が遠くなります。置換がすべて終わっても、全箇所が正しく修正され、余計なところまで書き換えていないかのチェックまで考えると、逃げ出したくなりますね。
対応するのが厄介な、されど実際のシステム開発ではありがちな要件ですが、Entity FrameworkとLINQを使っていれば、Visual Studioのリファクタリング機能で一発解決です(図4)。
リファクタリング機能により、エンティティクラスのプロパティ定義から、データ挿入、検索などでこのフィールドを使用している場所がすべて書き換えられますし、実行時にデータベース側の定義も自動的に書き換わります。この操作はVisual Studioにとっては「ProductクラスのNameプロパティ」の名前変更に過ぎません。ですから、仮に同じNameという名前のプロパティが他のクラス(=他のテーブル)に存在するとしても、全く影響はありません。C#プログラミングで当たり前に使っている機能が、データベースプログラミングでもそもまま流用できるあたり、「統合」言語クエリの底力を感じる瞬間ですね。
まとめ
本記事では、LINQとラムダ式の大まかな概念と、LINQを使ったクエリ方法について解説しました。ぜひLINQを恐れることなく、LINQメソッド式&ラムダ式を使って快適なデータベースプログラミングをお楽しみください。
次回は「出力されるSQLを見てみよう&遅延ローディングの光と闇(N+1問題)」と題して、LINQが実際にはどのようなSQLに変換されるのか、またパフォーマンス上の問題にどう対処するか、解説します。