'Create an expression dynamically to sort Linq to Entities ascending/descending based on a dynamically provided property name

Here is what I am trying to do. I have done a few simple expressions, but this one is a little too much for me right now.

    public static Expression<Func<IQueryable<TEntityType>, IOrderedQueryable<TEntityType>>> SortMeDynamically<TEntityType>(bool isAsc, string propertyname)
    {
        var param = Expression.Parameter(typeof(TEntityType), "x");
        var prop = Expression.PropertyOrField(param, propertyname);
        var sortLambda = Expression.Lambda(prop, param);

        string sortOrder = isAsc ? "OrderBy" : "OrderByDescending";

        var selector = Call(
        typeof(Queryable),
        sortOrder,
        new[] { prop.Type},
        sortLambda);

        var lambda = Lambda<Func<IQueryable<TEntityType>, IOrderedQueryable<TEntityType>>>(selector, param);
        return lambda;
    }

The error I get is the following.

System.InvalidOperationException: No generic method 'OrderBy' on type 'System.Linq.Queryable' is compatible with the supplied type arguments and arguments. No type arguments should be provided if the method is non-generic

The closest solution I found was the one below.

LINQ to Entities OrderBy Expression Tree



Solution 1:[1]

Calling the various .OrderBy linq methods dynamically is quite a pain.

The most difficult part of this process is locating the MethodInfo of the four relevant Queryable.OrderBy methods, with the correct generic constraints. There are a number of ways to achieve this.

You could pull the method out of a template Expression<Func<...>>, as in your linked answer;

Expression<Func<IOrderedEnumerable<TEntityType>>> sortMethod = 
    (() => query.OrderBy<TEntityType, object>(k => null));

var methodCallExpression = (sortMethod.Body as MethodCallExpression);
var method = methodCallExpression.Method.GetGenericMethodDefinition();

You could use Type.GetMethods and filter the results. Though you'd have to worry about future runtime changes breaking this approach.

var method = typeof(Queryable).GetMethods()
    .Where(m => m.IsGenericMethod
        && m.Name == nameof(Queryable.OrderBy)
        && m.GetParameters().Length == 2)
    .Single();

In both cases you'd then need to call .MakeGenericMethod to supply the correct generic parameters.

var genericSortMethod = method.MakeGenericMethod(typeof(TEntityType), prop.Type);

Or you could create a delegate and pull the method from there. Again, getting the generic constraints correct is a bit fiddly. But can be easier with a helper method. Which is similar to how the linq runtime locates this MethodInfo.

public MethodInfo GetOrderFunc<T, V>(Func<IQueryable<T>, Expression<Func<T, V>>, IOrderedQueryable<T>> func)
    => func.Method;

var genericSortMethod = GetOrderFunc<TEntityType, V>(Queryable.OrderBy);

If you don't know the argument value type, you could call that method via reflection.

Now you can either invoke the method;

var orderedQuery = (IOrderedQueryable<TEntityType>)genericSortMethod.Invoke(null, new object[] { query, sortLambda });

Or by reading the source code, recreate what those methods actually do.

var expression = query.Expression;

expression = Expression.Call(
    typeof(Queryable), 
    genericSortMethod,
    new Type[] { typeof(TEntityType), prop.Type },
    expression, 
    Expression.Quote(sortLambda));

var orderedQuery = (IOrderedQueryable<T>)query.Provider.CreateQuery<T>(expression);

No matter how you approach this, you need to take an IQueryable<TEntity> query parameter, and return an IOrderedQueryable<TEntity>. Or just stop at creating the Expression<Func<>>.

The other, other option is to move all the generic mucking around into a helper method. Then invoke that method via reflection. Obtaining the generic MethodInfo using one of the same approaches explained above.

public IOrderedQueryable<T> Order<T, V>(this IQueryable<T> query, Expression<Func<T, V>> key, bool then, bool desc)
    => (desc)
        ? (then ? ((IOrderedQueryable<T>)query).ThenByDescending(key) : query.OrderByDescending(key))
        : (then ? ((IOrderedQueryable<T>)query).ThenBy(key) : query.OrderBy(key));

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 marc_s