私が取り組んでいるプロジェクトでも同じことを行いました。ユーザーがUIで行った選択に基づいて、実行時にクエリが完全に作成されます。
System.Linq.Expressions
名前空間のクラスを使用して、式ツリーを使用してLINQクエリを作成します。非常に強力ですが、学習曲線が急です。
LINQPadを使用してクエリを記述し、式をダンプして、下にあるツリーがどのように見えるかを確認して、クエリを自分で作成する方法を知ることができます。
たとえば、LINQPadで次のコードを実行すると、式ツリーのダンプが生成されます。
var query = from p in Puzzles
select p;
query.Expression.Dump(20);

では、単純なLINQクエリを動的に作成するコードを実際にどのように作成するのでしょうか。
最も単純なクエリである次の例を考えてみましょう。
var query = from person in data
select person;
次のコードは、同等のクエリをその場で生成します。
using System;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
namespace TestLinqGenerator
{
class Program
{
static void Main(string[] args)
{
// Set up dummy data
var data = new[]
{
new {Name = "Fred"},
new {Name = "Simon"}
}.AsQueryable();
var dataType = data.ElementType;
// IQueryable: data
var source = Expression.Constant(data);
// Parameter: person
var parameter = Expression.Parameter(dataType, "person");
// person => person
var lambda = Expression.Lambda(parameter, parameter);
// Expression: data.Select(person => person)
var callSelect = Expression.Call(GetSelect().MakeGenericMethod(dataType, dataType), source, Expression.Quote(lambda));
// IQueryable: data.Select(person => person)
var query = data.Provider.CreateQuery(callSelect);
// Execute query
var results = query.Cast<object>().ToList();
}
private static MethodInfo GetSelect()
{
// Get MethodInfo of the following method from System.Linq.Queryable:
// public static IQueryable<TSource> Select<TSource>(this IQueryable<TSource> source, Expression<Func<TSource, bool>> predicate)
return typeof(System.Linq.Queryable).GetMethods().Where(
method => method.Name == "Select" && method.GetParameters().Length == 2 &&
method.GetParameters()[1].ParameterType.GetGenericArguments()[0].Name == typeof(Func<,>).Name).Single();
}
}
}
このコードをコンソールアプリケーションに貼り付けることで、このコードを実行できるはずです。デバッガーをステップスルーして、各ステップの機能を理解します。
追加情報
Reflectorの使用の実装をQueryable.Select
確認すると、クエリを動的に作成するときに何が必要かを理解するのに役立ちます。以下にコピーしました:
public static IQueryable<TResult> Select<TSource, TResult>(this IQueryable<TSource> source, Expression<Func<TSource, int, TResult>> selector)
{
if (source == null)
{
throw Error.ArgumentNull("source");
}
if (selector == null)
{
throw Error.ArgumentNull("selector");
}
return source.Provider.CreateQuery<TResult>(Expression.Call(null, ((MethodInfo) MethodBase.GetCurrentMethod()).MakeGenericMethod(new Type[] { typeof(TSource), typeof(TResult) }), new Expression[] { source.Expression, Expression.Quote(selector) }));
}
興味深いことに、の実装は、Queryable.Select
それ自体を呼び出すLINQ式表現を作成するだけです。LINQプロバイダーは、実際にその式を別のTSQLに変換します。メソッド自体はSelect
実際には選択を実行しません。
コードも同じことを行う必要があります-LINQ式を作成します。
単純な選択を行う方法に慣れたらQueryable.Where
、LINQクエリのミックスやその他の機能に追加する方法を検討できます。投影(select new {x, y, z}
など)は非常に難しいので、長持ちさせることをお勧めします。コンパイラが匿名型を生成するのとほぼ同じ方法で、実行時に型を生成する必要があります。System.Reflection.Emit
仕事のためのあなたのツールです。
このアプローチの優れた点の1つは、LINQ to Entities、LINQ to SQL、Mindscape Lightspeed、によって提供されるメモリ内LINQプロバイダー実装などの任意のLINQプロバイダーで使用できることですAsQueryable
。
LINQ式を生成する私のコードはIQueryableを受け入れ、実行時にこれは現在Mindscape Lightspeed IQueryablesで提供されていますが、他の1つである可能性もあります。次に、単体テストで、オブジェクトの配列を使用してテストデータを作成し、それをLINQ式ジェネレーターに渡されるIQueryable
usingに変換します。AsQueryable
私の単体テストでは、あらゆる範囲の複雑なクエリを生成できますが、データベースを必要とせずに簡単にテストできます。上記のサンプルは、これを行う方法を示しています。