はじめに
Linq to Entityを使用すると、静的型チェックを使用して複雑なクエリを非常に表現力豊かに作成できます。 ただし、クエリをもう少し動的にする必要がある場合があります。 たとえば、列名が文字列で指定されている場合、並べ替えを追加します。
つまり 次のように書きます:
var query = from customer in context.Customers select customer; //! . query = query.OrderBy("name"); var data = query.ToList();
この場合、式ツリーの動的な構築が助けになります。 確かに、式だけでは十分ではなく、テンプレートメソッドを動的に検索して構築する必要があります。 しかし、これはそれほど難しくありません。 以下の詳細。
フィールド名が文字列で指定されている場合、linqをエンティティクエリにソート
一般に、Linqクエリによって返されるデータを整理するために使用される4つのメソッドがあります。
OrderBy OrderByDescending ThenBy ThenByDescending
3つの引数を取る一般化されたメソッドを記述します。
public static IOrderedQueryable<T> ApplyOrder<T>( this IQueryable<T> source, string property, string methodName )
sourceは、順序を追加するソースIQueriableです。 property-ソートが実行されるプロパティの名前。 methodName-上記リストの順序付けメソッドの名前。 もちろん、バトルコードでは、ApplyOrderはプライベートになり、ユーザーはメソッドを処理します。
OrderBy (this IQueryable<T> source, string property) OrderByDescending (this IQueryable<T> source, string property) ThenBy (this IQueryable<T> source, string property) ThenByDescending (this IQueryable<T> source, string property)
簡単に配置され、最終的にApplyOrderを呼び出します。
public static IOrderedQueryable<T> ApplyOrder<T>( this IQueryable<T> source, string property, string methodName ) { // . x => x.property, var arg = Expression.Parameter(typeof(T), "x"); // . x => x. Expression expr = arg; // . property. x => x.property expr = Expression.Property(expr, property); // , var lambda = Expression.Lambda(expr, arg); // Queryable methodName. var method = typeof(Queryable).GetGenericMethod( methodName, // new[] { typeof(T), expr.Type }, // new[] { source.GetType(), lambda.GetType() } ); // , this . //.. source.OrderBy(x => x.property); return (IOrderedQueryable<T>)method.Invoke(null, new object[] { source, lambda }); }
コメントは何が起こっているかを説明しています。 最初に、ソートされたフィールドにアクセスする式ツリーが作成されます。 次に、式ツリーからラムダが作成されます。 次に、ラムダを受け入れることができるソート方法が構築されます。 そして、最終的に、このメソッドは動的に起動されます。
ここで最も難しい瞬間は、別の拡張メソッドGetGenericMethodで作成されるテンプレートメソッドの動的な作成です。
public static MethodInfo GetGenericMethod( this Type type, string name, Type[] genericTypeArgs, Type[] paramTypes ) { var methods = // from abstractGenericMethod in type.GetMethods(BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static) // where abstractGenericMethod.Name == name // generic- where abstractGenericMethod.IsGenericMethod // , let pa = abstractGenericMethod.GetParameters() // where pa.Length == paramTypes.Length // , select abstractGenericMethod.MakeGenericMethod(genericTypeArgs) into concreteGenericMethod // , where concreteGenericMethod.GetParameters().Select(p => p.ParameterType).SequenceEqual(paramTypes, new TestAssignable()) select concreteGenericMethod; // . return methods.FirstOrDefault(); }
ここでは、nameメソッドの場合、タイプクラスが作成され、2つのタイプリストがあります。 genericTypeArgsリストは、どのタイプのユニバーサルメソッドを作成する必要があるかを示し、paramTypesは、このメソッドが受け入れるタイプのパラメーターを示します。 それはすべてオーバーロードに関するものであり、メソッドは異なるシグネチャを持つことができるため、適切なものを選択する必要があります。 検索はc#オーバーロード解決のルールに完全に準拠しているのではなく、渡された値をメソッドの引数に割り当てることができるように考慮されるだけです。 そして、長く考えずに、条件を満たした最初の過負荷がとられます。 あるタイプの値を別のタイプに割り当てる可能性に関するタイプの比較は、特別なクラスTestAssignableによって実行されます。
private class TestAssignable : IEqualityComparer<Type> { public bool Equals(Type x, Type y) { // y x, return x.IsAssignableFrom(y); } public int GetHashCode(Type obj) { return obj.GetHashCode(); } } : var context = new Models.TestContext(); var query = from customer in context.Customers select customer; query = query .ApplyOrder("name", "OrderBy") .ApplyOrder("surname", "ThenBy") .ApplyOrder("id", "ThenByDescending"); var data = query.ToList();
最小限の変更で示されているアプローチは、オブジェクトへのLinqを操作するためにIEnumerable <>に適応できます。