IQueryableには、何らかのLINQプロバイダーが関連付けられている。AsQueryableを使用してIQueryableに変換したクエリは、以下のように「EnumerableQuery<T>」というLINQプロバイダーが割り当てられている。
IQueryableインターフェイスとLINQプロバイダーの関係や、プロバイダーの作成方法については、チュートリアル : IQueryable LINQ プロバイダの作成にまとまっている。やや難解であるので、読み合わせ的に補足していこうと思う。
想定しているシナリオ
「TerraServer-USA Web Services」というサービスが存在したとして(実際あるようだが、何故か応答が返ってこない)、そのウェブサービスに対してクエリを発行し、結果を取得出来る。何もない状態では、WCFで定義されたインターフェイスに従って、メソッド呼び出しのような形式でサービスを利用するが、このサービスに対応するLINQプロバイダを書けば、LINQクエリの記述でサービスを利用する事が可能になる。つまり、既存のWCFインターフェイスを使用して、「LINQ to TerraServer」を作ると言う事になる。
全体的な構造
LINQクエリを記述する側は、IEnumerableやIQueryableといったインターフェイスにだけ注目する。LINQプロバイダーを作る場合(そして、LINQ to SQLやLINQ to Entities)は、IQueryableインターフェイスを実装した独自のクラスを用意する。と同時に、LINQプロバイダーとなる「IQueryProvider」インターフェイスを実装したクラスも用意する。このクラスが、LINQクエリーのExpressionを解釈し、SQL文を生成したりする。今回はWCFを使用して、TerraServerのサービスにアクセスする。
また、LINQクエリの記述を成立させるには、結果を格納するエンティティとなるクラスが必要だ。例えば、
using (var context = new AdventureWorksLT2012_DataEntities()) { // CustomerAddressエンティティクラスが受け皿となるクエリ IQueryable<CustomerAddress> persons = from customerAddress in context.CustomerAddress where customerAddress.AddressType == "Shipping" select customerAddress; }
このようなクエリが(RDBやWCFを通じてサーバーで)実行され、結果が返される。その結果をこのクラス(CustomerAddress)に格納する。そのため、クラスの定義が必要となる。
もう一つ、LINQ to SQLやLINQ to Entitiesでは、「コンテキスト」と呼ばれるRDBへの接続を管理するクラスも必要だ。これがないと、LINQソースとなる端点(上記の例であれば、context.CustomerAddress)が提供出来ないので、クエリが書けない。但し、別の方法もある。普通の列挙子をAsQueryableでIQueryableに変換した場合は、コンテキストとなるクラスは存在しない。つまり、何らかの方法で、LINQソースとなる端点が提供できれば良いと言う事だ。