Complex LINQ sorting using Lambda Expressions

.net c# expression-trees linq

Question

Does anyone have/know of an IQueryable.OrderBy extension that takes an Expression (retrieved, for example, by Reflection)? I believe the function would look something like this:

public static IQueryable<TEntity> OrderBy<TEntity>
    (this IQueryable<TEntity> source, Expression sortExpression)

Expression would be assumed to be an Expression<Func<TEntity, T>> where TEntity is the same object being sorted on, and T is a type that needs to be determined in order to create the new IQueryable.

I've found many examples of extensions that take a string, including Dynamic Linq, like this:

public static IQueryable<TEntity> OrderBy<TEntity>(
    this IQueryable<TEntity> source, string sortExpression) 

If it's possible to take the string and use Reflection to look up the type from the object in question, it should also be possible to take the Expression, and get the value type which is right there IN the Expression.


Following is a detailed explanation of why I'd like to have this, which you may or may not need.

I have a rather large list of complex records to sort. Because the list is so long, I prefer to have the sorting done on the database side. To handle more complex properties, I've created Expressions that provide the sorting functionality, like so:

if (model.sortExpression == "PlannedValue")
{
    Expression<Func<BDopp, decimal>> sorter = BDopp.PlannedValueSorter;         
    if (model.sortDirection == "DESC")
        opps = opps.OrderByDescending(sorter).AsQueryable();
    else
        opps = opps.OrderBy(sorter).AsQueryable();
}

BDOpp.PlannedValueSorter retrieves a static expression from the object which allows sorting to be done without opps are still of type IQueryable:

public static Expression<Func<BDopp, decimal>> PlannedValueSorter
    {
        get
        {
            return z => z.BudgetSchedules
                .Where(s => s.Type == 1)
                .Sum(s => s.Value * s.Workshare * z.valueFactor / 100 / 100);
        }
    }

Sorting for simple properties is done with Extension methods that use Reflection to build an expression based on the name of the property passed as a string.

This works well, but for the complex types, I still need branching logic, and I'd rather not do that. What I'd rather do is check for a static property containing the expression, and then simply apply it. I can get the expression like this:

PropertyInfo info = typeof(BDopp).GetProperty(model.sortExpression + "Sorter",
    BindingFlags.Static | BindingFlags.Public);
Expression expr = (Expression)info.GetValue(null, null);

For the PlannedValue property, this gets me the expression sorted in PlannedValueSorter, which I already know works.


Update:

Various digging around has gotten me what I think might be some progress:

public static IQueryable<TEntity> OrderBy<TEntity>(this IQueryable<TEntity> source, 
    Expression<Func<TEntity, dynamic>> sortExpression)
    {
        var unary = sortExpression.Body as UnaryExpression;
        Type actualExpressionType = unary.Operand.Type;

actualExpressionType is in fact the return type of the Expression (for this particular property, it's decimal).

Unfortunately I'm mostly just working by trial and error, since I haven't yet wrapped my brain around how all this works, so my attempt to update the query like so is not working:

        MethodCallExpression resultExp = Expression.Call(typeof(Queryable), 
            "OrderBy",
            new Type[] { typeof(TEntity), actualExpressionType },
            source.Expression, sortExpression);
        return source.Provider.CreateQuery<TEntity>(resultExp);

It compiles okay, but the following error is thrown at runtime:

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.

Popular Answer

Okay, I've got a solution:

public static IQueryable<TEntity> OrderBy<TEntity>(
    this IQueryable<TEntity> source, 
    Expression<Func<TEntity, dynamic>> sortExpression, 
    bool descending)
    {
        var unary = sortExpression.Body as UnaryExpression;
        var operand = unary.Operand;
        Type actualExpressionType = operand.Type;

        MethodCallExpression resultExp = 
            Expression.Call(typeof(Queryable), 
                descending? "OrderByDescending" : "OrderBy",
                new Type[] { typeof(TEntity), actualExpressionType },
                source.Expression, 
                Expression.Lambda(operand, sortExpression.Parameters));
        return source.Provider.CreateQuery<TEntity>(resultExp);
    }

The bool descending is to allow for the standard OrderBy and OrderByDescending overloads. Two major breakthroughs here, at least for me:

  1. Getting the Operand out of the expression.
  2. Using Expression.Call and Expression.Lambda to create the new expression - this allows me to use an actual "Type" variable, whereas the Expression<Func<TEntity, T>> syntax requires you to use a type that's known at compile time.


Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow
Is this KB legal? Yes, learn why
Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow
Is this KB legal? Yes, learn why