LINQ Project では In-Memory Query と DLINQ Query に同じ Query Expression が使えます。 コンパイラは Query Expression が In-Memory に対してかそれとも Relational DB なのか分かるのか?と言う質問をよくされます。 コンパイラがすごい事をしていると誤解されているようですので、その説明をちょっとだけ。
特に Magic は無く、Trick としては C# 3.0 で新しく追加された
1. Query Operators はオーバーロード可能
2. lambda 式は Expression<Func<...>> にアサインされた場合 Expression Tree を IL の代わりに Emit する
の二つだけです。例えば、LINQ In-Memory Query の場合
var q = from x in new int[]{1,2,3,4}
where x > 2
select x;
は
int[] q = (new int[]{1,2,3,4}).Where(x => x > 2);
に、まず変換されます。ここで使われる Where Operator は
IEnumerable<T> Where<T>(this IEnumerable<T> source, Func<T, bool> predicate)
です。 理由は 普通に Overload の解決で IEnumerable<T> へ Implicitly Convertable な new int[] が最初の Where Operator の引数だからからです。
では、DLINQ Query の場合を見てみると、
var q = from x in db.Customer
where x.ContactName == "Hoge"
select x;
は
q = db.Customer.Where(x => x.ContactName == "Hoge");
にと書き換えられ、
Query<T> Where<T>(this Query<T> source, Expression<Func<T, bool>> predicate)
の Where Operator が使われます。Trick 1で書いたQuery Operator は Overload 可能がキーポイントです。 最初のdb.Customer プロパティ の戻り値の型は Table<T>。 クラスの関係は
IEnumerable<T>
|
Query<T>
|
Table<T>
よって、Overload の解決は Table<T> により近い Query<T> へと、つまり、Where<T>(this Query<T>..) の方が Where<T>(this IEnumerable<T> ...) よりも Better となる訳です。 ここで先ほどの Trick の2番目、lambda 式の Expression<Func<..>>への変換が登場します。 Query Expression の中の
x => x.ContactName == "Hoge"
は
Query<T> Where<T>(this Query<T> source, Expression<Func<T, bool>> predicate)
の Where Operator が使われる事により、2番目の引数、Expression<Func<T, bool>> へとアサインされます。 lambda 式は Expression<Func<...>> にアサインされた事により無事にExpression Tree に書き出される事になります。
Dlinq Engine は Expression Tree を元に C# から SQL の変換を行い Database へ SQL Query をなげ、結果を得ます。
このように、LINQ は既存の言語の仕様の上に成り立っていると言うのが分かっていただけたかと思います。