📜 ⬆️ ⬇️

Dynamic Linq Queries or Tame Expression Trees

Introduction


Linq to Entity allows you to write complex queries very expressively with static type checking. But sometimes you need to make requests a little more dynamic. For example, add sorting when the column name is specified as a string.
Those. write something like:
var query = from customer in context.Customers select customer; //!  . query = query.OrderBy("name"); var data = query.ToList(); 

In this case, the dynamic construction of expression trees will help. True, expressions alone will not be enough; you will need to dynamically find and construct template methods. But this is not so difficult. Details below.


Sort linq to entity queries when the field name is specified as a string


In general, 4 methods are used to organize the data returned by the Linq query:
 OrderBy OrderByDescending ThenBy ThenByDescending 

We write a generalized method that will take 3 arguments.
 public static IOrderedQueryable<T> ApplyOrder<T>( this IQueryable<T> source, string property, string methodName ) 

Where source is the source IQueriable to which you want to add ordering; property - the name of the property by which sorting is performed; methodName is the name of the ordering method from the list above. Of course, in the combat code, ApplyOrder is made private, and the user deals with the methods:
 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) 

Which are arranged trivially and as a result cause 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 }); } 

Comments explain what is happening. First, an expression tree is created in which the field to be sorted is accessed. Then a lambda is created from the expression tree. Next, a sorting method is constructed that can accept a lambda. And, finally, this method is dynamically launched for execution.
The most difficult moment here is the dynamic creation of a template method, which is rendered into a separate extension method 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(); } 

Here, the name method is created for the type class, and there are 2 types of lists. The genericTypeArgs list specifies for which types a generic method should be created, and paramTypes shows the parameters of which types this method should accept. It's all about overloading, sometimes the method can be with different signatures, and we need to choose the right one. The search is not quite according to the c # overload resolution rules, it is only taken into account, so that you can assign the passed values ​​to the method arguments. And then, not long, the first overloading that satisfies the conditions is taken. Comparing types for the possibility of assigning a value of one type to another is performed by a special class 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(); 

The shown approach with minimal changes can be adapted to IEnumerable <> for working with Linq to objects.

')

Source: https://habr.com/ru/post/181065/


All Articles