とうとう、LINQプロバイダーの中心部へと来た。今までIQueryableが収集したExpressionを探索したり変形したりしたが、結局まだ検索条件の解析をしていなかった。ここで「LocationFinder」クラスを使用して、検索条件の解析を行う。名前からはピンと来ないが、このクラスが中心的存在だ。
このクラスは例によってExpressionVisitorクラスを継承しており、前述のInnerMostWhereFinderクラスとEvaluatorクラスによって得られたExpressionを探索する。
TerraServerサービスから返される結果は、「Place」クラスに格納される。Placeクラスはエンティティクラスであり、「Name」プロパティと「State」プロパティを持つ。TerraServerのサービスは、任意の場所を示す文字列を与えることで、その場所に応じたレコード群が返される。そこで、Placeクラスのプロパティにアクセスする式を書けば、それが検索条件として反映されるようにすればよい。
// コンテキストを生成 var terraPlaces = new QueryableTerraServerData<Place>(); // コンテキストに検索条件を与え、TerraServerサービスにクエリを発行し、結果を取得する var query = from place in terraPlaces where place.Name == "Johannesburg" select place;
上記の例は、Nameプロパティが”Johannesburg”と一致するかどうかを確認している。同様に、
// Stateで検索 var query = from place in terraPlaces where place.State == "Kentucky" select place;
でも検索できるようにする。
BinaryExpressionの解析
上記のクエリ構文をメソッド構文、かつ拡張メソッドではない構文に直すと、以下のようになる。
// 拡張メソッドではないメソッド構文 var query = Queryable.Where( terraPlaces, place => place.Name == "Johannesburg");
これらとExpressionがどのように対応するかを示したのが、以下の図だ。
さて、ここで注意点がある。比較演算子の場合、右辺と左辺は入れ替えることが可能だ。つまり、
// コンテキストに検索条件を与え、TerraServerサービスにクエリを発行し、結果を取得する var query = from place in terraPlaces where "Johannesburg" == place.Name // 右辺と左辺が逆 select place;
のようなクエリを記述しても、これを許容しなければならない。BinaryExpressionクラスには「Right」と「Left」プロパティがあり、演算子の右辺と左辺のExpressionに対応している。従って、これらに設定されているExpressionが逆であっても、正しく識別しなければならない。
サンプルコード中では「ExpressionTreeHelpers」クラスの「IsMemberEqualsValueExpression」メソッドや「GetValueFromEqualsExpression」メソッドが該当する。逆に定義されていても問題がないように実装されている事が分かる。
まとめると:
- 二項演算子(BinaryExpression)であり、
- 二項の片方がPlace.Name、又はPlace.Stateプロパティであり、
- もう片方が固定値(Constant)
であれば、この固定値が検索したい値と言う事になる。LocationFinderクラスはこのような探索を行い、用意しておいたリストに加える。
全てのExpressionノードを探索すれば、リストに検索条件となる値群が抽出されていることになる。