この記事は会員限定です。会員登録(無料)すると全てご覧いただけます。
.NET Framework 3.5(=Visual Studio 2008)以降では、LINQ(Language INtegrated Query)機能がC#言語やVB言語に導入されている。LINQを使うと、SQL文ライクな構文のプログラム・コードを記述することで、オブジェクト配列やXML、データベースなどに対するクエリ(=データ取得)を効率的に行える。従来のように、SQL文を文字列で記述してクエリする場合と比べて、コードがかなり短くなる。
●LINQの問題と解決方法
しかしその手軽さの半面、欠点もある。一番大きな問題は、(簡単には)動的にクエリを組み立てられないことだ。
例えばキーワード検索で、そのキーワードが1つなのか10個なのか事前に決まっていない場合などではLINQは使いにくい。従来の文字列のSQL文であれば、文字列を連結しながら動的にWhere句を組み立てればよかったが、LINQの場合、ソース・ファイルにLINQ文がハード・コーディングされるため、実行時に動的にそれを変更することができない。
この問題で悩む開発者は多く、実際にさまざまな解決方法が提示されている。
動的にLINQ文を組み立てるには、通常のクエリ構文(from/select/whereキーワードなど)のLINQでハード・コーディングするのではなく、メソッド構文(Select/Whereメソッドなど)のLINQ(=メソッド・ベースのLINQ)を利用する必要がある。例えばWhereメソッドを使うことで、その引数に動的な値(=式ツリー)を指定できるようになる。メソッド・ベースのLINQで動的に式ツリーを組み立てる解決方法を2つほど紹介しよう。
1つ目が、こちらのブログ記事で紹介されている「DynamicLINQ」という拡張メソッドを実装したクラスを利用して、(例えば)Whereメソッドの引数を文字列として記述する方法だ。文字列なので、動的に式を組み立てられるというわけだ。
2つ目が、「XMLを扱えるLINQ ―LINQ to XML― の基礎を学ぼう」の「○ラムダ式を動的に組み上げる方法」で紹介されている、式ツリーをそのままプログラム・コードで動的に作成する方法だ。
1つ目のDynamicLINQを筆者が試したところ、文字列が解析されてから式ツリーへの変換処理が行われるため、(解析後にどのメソッドが呼ばれるかが簡単に予想できないので)デバッグしにくい。これだと、例えば何らかのエラーが発生したときに、そのデバッグに余計に時間が掛かってしまう可能性がある。また現時点では、DynamicLINQはサンプルという扱いであり、不足する機能が出てきたら自分で拡張しなければならない。DynamicLINQを拡張していくのは、以下で紹介する手法よりもハードルが高く、あまりお勧めできない。
それに対し、2つ目の手法はストレートに動的LINQを実現できる。メソッド・ベースのLINQコードのラムダ式部分、例えば左辺のパラメータ、右辺の式、さらに式と式を条件AND(=論理積)演算子(&&/AndAlso)で結合した式、というような感じでコーディングすればよい。ラムダ式の内容をストレートにコーディングするので、デバッグもしやすい。もちろんさまざまな意見や好みはあるだろうが、筆者はこの2番目の手法をお勧めする。
そこで以下では、2番目の方法を説明する。
●ラムダ式を動的に組み立てて式ツリーを取得する方法(Whereメソッドの場合)
ここでは、プログラム・コードでWhereメソッドの引数として指定する式ツリー型オブジェクト、具体的にはExpression<TDelegate>オブジェクト(System.Linq.Expressions名前空間)を動的に組み立てる方法を説明する。
まず、静的なラムダ式でハード・コーディングしたメソッド・ベースのLINQコードの例を示そう。
using System;
using System.Linq;
static class Program
{
static void Main()
{
// クエリ対象となるデータソース
string[] dataSource =
{ "apple", "orange", "peach", "melon", "grape" };
// 検索キーワードを3つ取得
Console.WriteLine("指定した文字を含む文字列を検索します。");
ConsoleKeyInfo info = Console.ReadKey();
string character = info.KeyChar.ToString();
Console.WriteLine("かlかoを含むものを検索。");
string[] keywords = { character, "l", "o" };
// メソッド・ベースのLINQ文(静的なラムダ式を利用)
var query = dataSource.AsQueryable().
Where(item =>
item.ToLower().Contains(keywords[0].ToLower()) ||
item.ToLower().Contains(keywords[1].ToLower()) ||
item.ToLower().Contains(keywords[2].ToLower())).
OrderByDescending(item => item);
// 検索された用語を出力
foreach (string term in query)
{
Console.WriteLine(term);
}
// 出力例(「m」を入力した場合):
// 指定した文字を含む文字列を検索します。
// mかlかoを含むものを検索。
// orange
// melon
// apple
// 出力を確認するために実行を止める
Console.ReadKey();
}
}
Sub Main()
' クエリ対象となるデータソース
Dim dataSource As String() = _
{"apple", "orange", "peach", "melon", "grape"}
' 検索キーワードを3つ取得
Console.WriteLine("指定した文字を含む文字列を検索します。")
Dim info As ConsoleKeyInfo = Console.ReadKey()
Dim character As String = info.KeyChar.ToString()
Console.WriteLine("かlかoを含むものを検索。")
Dim keywords As String() = {character, "l", "o"}
' メソッド・ベースのLINQ文(静的なラムダ式を利用)
Dim query = dataSource.AsQueryable(). _
Where(Function(item) _
item.ToLower().Contains(keywords(0).ToLower()) OrElse _
item.ToLower().Contains(keywords(1).ToLower()) OrElse _
item.ToLower().Contains(keywords(2).ToLower())). _
OrderByDescending(Function(item) item)
' 検索された用語を出力
For Each term As String In query
Console.WriteLine(term)
Next
' 出力例(「m」を入力した場合):
' 指定した文字を含む文字列を検索します。
' mかlかoを含むものを検索。
' orange
' melon
' apple
' 出力を確認するために実行を止める
Console.ReadKey()
End Sub
Copyright© Digital Advantage Corp. All Rights Reserved.