ちょっと複雑な検索をしてみる
続いて、もう少し複雑なパターンで検索してみましょう。検索画面で多数の検索条件がある場合、「条件Aが指定されていればAで検索、Bが指定されていればさらにBで検索……」といった処理を書くことが良くあります。たとえば「担当者名が入っていたら部分一致検索、価格が入っていたら価格フィールドで検索、両方とも指定されていればAND検索」というパターンを書いてみましょう(リスト6)。
private IQueryable<Product> Search(CodeZineSampleContext context, string employeeName, int? price) { IQueryable<Product> query = context.Products; //(1)まずは全件取得 if (!string.IsNullOrEmpty(employeeName)) //(2)担当者名が指定されていれば { //(3)担当者名部分一致検索 query = query.Where(x => x.Employee.Name.Contains(employeeName)); } if (price.HasValue) //(4)価格が指定されていれば { query = query.Where(x => x.Price == price.Value); //(5)価格で検索 } return query; //クエリを返す }
(1)ではまず条件なしで全件をqueryに代入しています。
(2)、(3)では、担当者名が指定されている場合だけ、Whereメソッドで部分一致検索します。
(4)、(5)も同様に、価格が指定されている場合だけ、Whereメソッドで価格検索します。
勘の鋭い方は「あれ? (1)で全件検索してるんだから、その後にWhereメソッドで条件を指定しても意味ないのでは?」と思われたかもしれません。実はLINQは、書いた時点でその処理が実行されるのではなく「実際にクエリ結果が使われる時になって初めてクエリを処理する」という特徴を持っています。したがって、(1)では実際に全件取得が行われるわけではありません。
そして注目したいのは、両方の検索条件が指定された場合の(3)と(5)です。この場合、Whereメソッドが複数回呼ばれて、いわば積み重ねられることになります。このように、メソッドを積み重ねることを一般にメソッドチェーンと呼びます。JavaScriptライブラリのjQueryなどで多用される手法ですが、LINQメソッド式でも同様にメソッドを繋いでいく(チェーン)ことができます。この積み重ねは何回でも繰り返すことができますので、もっと多くの検索条件が指定された場合でも、Whereメソッドを自由に積み重ねていくことができます。積み重ねられたWhereメソッドは最終的にクエリが使用される時点でSQLに変換され、データベース上で実行されます。
大量の検索条件を持つ検索メソッドのSQLを手動で書くことを考えたら気が遠くなりますが、LINQであれば必要に応じて指定された条件のみをWhereメソッドで積み重ねていくことで、条件の増減に容易に対応することができます
LINQの他の機能
ここまでは、LINQメソッド式の解説と言いつつ、Whereメソッドばかりを説明していました。LINQメソッド式ではそれ以外にも、特定のフィールドだけを選択するSelectメソッド、グルーピングするためのOrderByメソッドなどがあります。
Selectメソッド
Selectメソッドは、テーブルの特定のフィールドを選択するためのメソッドです。Selectメソッドを使用しない場合は、テーブルの全フィールドが選択されますが、Selectメソッドを使用することで、特定のフィールドのみを選択することができます。SQLのSELECT文で「*(全フィールドを選択)」を使うか、使用するフィールドのみを指定するかの違いと考えれば良いでしょう。
リスト7は、Selectメソッドで商品の名前だけを取得するサンプルです。
//Selectメソッドで商品名だけ選択する var names = context.Products.Where(x => x.Price < 300).Select(x => x.Name); //コレクションを回して foreach (var name in names) { //Nameを取り出す Console.WriteLine("Name: {0}", name); }
Selectメソッドのラムダ式では、取得したいプロパティを指定します。ここでは、Nameプロパティだけを選択しています。戻り値はIQueryable<string>という、文字列のコレクションとなりますので、通常通りforeachで回して処理が可能です。
複数のフィールドを取得したい場合はリスト8のようになります。
//Selectメソッドで、NameプロパティとPriceプロパティだけを取り出す //戻り値はNameプロパティとPriceプロパティを持つ匿名型のコレクション var namePriceList = context.Products.Where(x => x.Price < 300).Select(x => new { x.Name, x.Price }); //コレクションを回して foreach (var obj in namePriceList) { //NameとPriceを取り出す Console.WriteLine("Name: {0}, Price: {1}", obj.Name, obj.Price); }
ここでは、Name(商品名)とPrice(価格)プロパティを取り出しています。Selectメソッドのラムダ式の戻り値の「new { x.Name, x.Price }」という見慣れない記法に戸惑うかもしれません。これは、C#の匿名型と呼ばれる機能で、NameとPriceというプロパティを持つクラスをその場で定義しています。匿名型は、何度も使い回すことが無い、使い捨てるようなクラスをわざわざ定義することなく、その場で定義して返すことのできる便利な機能です。
戻り値はこの匿名型のコレクションとなりますが、匿名型はその名の通り型名を持ちませんので、戻り値はvarキーワードで受ける必要があります。使用する場合にはコレクションを回して、普通にName、Price両プロパティにアクセスできますので、匿名型という新しい機能であっても、それほど恐れる必要はありません。
Selectメソッドは、テーブルの必要なフィールドだけを取得できる便利なメソッドですが、Entity Frameworkにおいては、エンティティクラスのインスタンスとテーブルのレコードを対応させて考えるのが基本となるため、Selectメソッドを使わないケースも多いです。
本連載では基本的にSelectメソッドを使わず、レコードの全フィールドを取得してエンティティクラスのインスタンスとして扱う方法を使用します。実アプリケーションにおいては、「ドロップダウンリストの表示名と値だけを取得したい」場合など、レコードをエンティティクラスのインスタンスとして取得する必要が無いケースにSelectメソッドを使う場合があります。