目次

概要

LINQ to SQL で使われる Table クラスなどは IQueryable と IQueryProvider インターフェースを実装しています。 これら IQueryable および IQueryProvider は、

  • LINQ クエリ式から「式木」を構築する。

  • 構築した式木を解釈して、独自のクエリ処理を行う。

というような機能を提供するインターフェースです。 一度、式木(実行可能コードではなくて、プログラム中で読めるデータ)になるので、 IQueryable の実装次第で様々な機能を提供することができます。

となると当然、IQueryable を実装して、独自の LINQ プロバイダを作成したいとき、

  • クエリ式 → 式木の構築手順

  • 式木を独自に処理

の2つのことを理解しておく必要があります。

後者は要するに、式木に関する理解があればできることです。 なので、ここでは、前者の「クエリ式 → 式木構築」を中心に、 IQueryable の仕組みについて説明します。

式木に関しては、 「式木(Expression Trees)」や「[サンプル] 式木を WPF で GUI 表示」辺りを参考にしてください。

LINQ to SQL: クエリ式 → 式木 → SQL 文

まず、 前節で説明した「クエリ式 → 式木の構築手順 → 式木を独自に処理」という流れを見るために、 LINQ to SQL を例に説明します。

例えば、C# で以下のようなクエリ式を書いたとします。

var context = new CharacterContext("characters.sdf");

System.Linq.IQueryable q =
    from c in context.Characters
    join cv in context.CvList on c.CharacterVoiceId equals cv.ID
    select new
    {
        Name = c.姓 + c.名,
        Info = c.Infomation,
        Supplement = c.Supplement,
        CharacterVoice = cv.姓 + cv.名,
    };

IQueryable には Expression プロパティがあって、 これを使って、クエリ式 → 式木の構築結果を取得することができます。

System.Linq.Expressions.Expression e = q.Expression;
Console.Write(e);
Table(Character).Join(Table(CharacterVoice), c => c.CharacterVoiceId, cv => cv.I
D, (c, cv) => new <>f__AnonymousType0`4(Name = (c.姓 + c.名), Info = c.Infomatio
n, Supplement = c.Supplement, CharacterVoice = (cv.姓 + cv.名)))

テキストだといまいちわかりづらいと思うので、 この結果をツリー表示すると、以下のような感じ。

IQueryable.Expression の例
IQueryable.Expression の例

ここまでは LINQ to SQL に限らず、 IQueryable を実装する LINQ プロバイダでほぼ共通の処理です。

で、LINQ to SQL では、この式木を解析して、 以下のような SQL 文に変換します。

SELECT
    [t0].[姓] + [t0].[名] AS [Name],
    [t0].[学籍番号等] AS [Info],
    [t0].[補足] AS [Supplement],
    [t1].[姓] + [t1].[名] AS [CharacterVoice]
FROM [Characters] AS [t0], [CvList] AS [t1]
WHERE [t0].[cv] = [t1].[ID]

IQueryable, IQueryProvider

IQueryable および IQueryProvider は以下のようなインターフェースです。

public interface IQueryable : IEnumerable
{
  Type ElementType { get; }
  Expression Expression { get; }
  IQueryProvider Provider { get; }
}

public interface IQueryable<T> : IEnumerable<T>, IQueryable, IEnumerable
{
}
public interface IQueryProvider
{
  IQueryable CreateQuery(Expression expression);
  IQueryable<TElement> CreateQuery<TElement>(Expression expression);
  object Execute(Expression expression);
  TResult Execute<TResult>(Expression expression);
} 

IQueryable の方は特別な処理をしているわけではなく、 実際にクエリ式 → 式木構築などの処理を行っているのは IQueryProvider の方です。 IQueryProvider の方だけ差し替えて様々な LINQ プロバイダを作れるようになっています。

大まかに言うと、 CreateQuery で「クエリ式 → 式木の構築」を、 Excute で「式木の独自処理」を行います。

IQueryable, IQueryProvider の実装

たいていの場合、IQueryProvider.Excute 以外の部分の実装で凝る必要はないようです。 以下の記事(英語)に、IQueryable の典型的な実装方法が書かれています。

この記事では、IQueryable の実装である Query クラスは(継承とか不要で)このまま使いまわせるように作られています。

IQueryProvider の実装である QueryProvider の方では、 Execute メソッド(と、ToString などの際に必要になる GetQueryText メソッド)だけが抽象メソッドとして残されています。 独自の LINQ プロバイダを実装したい場合、 QueryProvider を継承して Execute と GetQueryText を実装することになります。

前節で説明したように、Execute は「式木の独自処理」の部分を担うメソッドで、 残りの部分はすでに実装されています。 すなわち、「クエリ式 → 式木の構築」の部分は、 この記事中の Query、QueryProvider クラスが全部実装してくれています。

ということで、このソースコードを使って、 IQueryable の「クエリ式 → 式木の構築」の部分について説明していきたいと思います。

挙動の確認

QueryProvider クラスの時点で「クエリ式 → 式木の構築」の部分は完成しているわけで、 「式木の独自処理」が必要ないのであれば、 以下のような適当な実装でも十分に動いたりします。

(foreach したりには使えないけども、Expression を作るのには使える。)

public class TestProvider : QueryProvider
{
    public override string GetQueryText(Expression expression)
    {
        return string.Empty;
    }

    public override object Execute(Expression expression)
    {
        return null;
    }

    public static IQueryable<T> CreateQueryable<T>()
    {
        return new Query<T>(new TestProvider());
    }
}

とりあえず、これを使って QueryProvider クラスの挙動を確認してみましょう。 以下のようなコードを実行してみます。

var q1 = TestProvider.CreateQueryable<int>();
Console.Write("{0}\n", q1.Expression);

var q2 = q1.Where(x => x > 10);
Console.Write("{0}\n", q2.Expression);

var q3 = q2.OrderBy(x => x);
Console.Write("{0}\n", q3.Expression);

var q4 = q3.Select(x => x * x);
Console.Write("{0}\n", q4.Expression);

実行結果は以下の通り。


.Where(x => (x > 10))
.Where(x => (x > 10)).OrderBy(x => x)
.Where(x => (x > 10)).OrderBy(x => x).Select(x => (x * x))        

要するに、Where, Select, OrderBy などの拡張メソッドを通るたびに、 IQueryable.Expression の中身が追記されています。

System.Linq.Queryable

実のところ、QueryProvider クラスの CreateQuery メソッドは、 引数で与えられた式木をそのまま Query クラスに流しているだけだったりします。

IQueryable<S> IQueryProvider.CreateQuery<S>(Expression expression)
{
    return new Query<S>(this, expression);
}

で、実際に「クエリ式 → 式木の構築」を担っているのは、 System.Linq.Queryable 中で定義された Select や Where 拡張メソッドの方です。

例えば、System.Linq.Queryable.Where の中身は概ね以下のようになっているようです。

public static IQueryable<T> Where<T>(
  this IQueryable<T> q,
  Expression<Func<T, bool>> pred)
{
  MethodInfo generic = (MethodInfo)MethodBase.GetCurrentMethod();
  MethodInfo method = generic.MakeGenericMethod(typeof(T));

  return q.Provider.CreateQuery<T>(
    Expression.Call(
      method,
      q.Expression,
      Expression.Quote(pred)
      ));
}

引数 q の持っている式木に、 Where メソッド自身の Call をかぶせた式木を作って QueryProvider の CreateQuery メソッドに渡します。

Select などの他の標準クエリ演算子でもほぼ同様の処理をしています。

更新履歴

ブログ