Search code examples
c#linqlambdaexpression-trees

Return Expression from a method to be used in OrderBy


I want to write an abstract method to be overridden by child classes, and what the method does is return an expression to be subsequently used in LINQ OrderBy(). Something like this:

Note: Message inherits from Notes class, and MyModel inherits from MsgModel.

public class Notes
{
    // abstract definition; using object so that I can (I hope) order by int, string, etc.
    public abstract Expression<Func<MsgModel, object>> OrderByField();
    // ...

    private string GetOrderByFieldName()
    {
        // I am not sure how to write this
        // This is my problem 2. Please see below for problem 1 :-(

        var lambda = OrderByField() as LambdaExpression;

        MemberExpression member = lambda.Body as MemberExpression;

        PropertyInfo propInfo = member.Member as PropertyInfo;

        return propInfo.Name;
    }
}

public class Message : Notes
{
    // second type parameter is object because I don't know the type
    // of the orderby field beforehand
    public override Expression<Func<MyModel, object>> OrderByField()
    {
        return m => m.item_no;
    }
}

Now if I try to order by this way:

var orderedQuery = myQueryable.OrderBy(OrderByField());

I get this error:

'Unable to cast the type 'System.Int32' to type 'System.Object'. LINQ to Entities only supports casting EDM primitive or enumeration types.'

I can rightaway say that type parameter object is the cause of the problem. So, when I change the type parameter to int, it works fine as long as I am ordering by an int field (e.g. the field item_no).

Q1. How can I get this to work? Surely, I can use a string property OrderByField instead of that expression-returning method and order by it, probably by writing some extension method for IQueryable (maybe using this great answer). But I want to have more intellisense while setting the order by.

Q2. How can I get the name of the order by column from the expression returned by the method OrderByField(). Obviously what I have tried with doesn't work. The member always gets null.


Edit: I have made some changes to the type parameters of the methods. Sorry for not doing it the first time.


Solution

  • Apparently the Expression<Func<T, object>> is not equivalent of Expression<Func<T, K>>, hence cannot be used as direct replacement of the later required by Queryable.OrderBy<T, K> and similar methods.

    Still it's possible to make it work with the help of Expression class by creating a non generic LambdaExpression via Expression.Lambda method and dynamically emitting a call to the corresponding Queryable method.

    Here is all that encapsulated in a custom extension methods (a modified version of my answer to How to use a string to create a EF order by expression?):

    public static partial class QueryableExtensions
    {
        public static IOrderedQueryable<T> OrderBy<T>(this IQueryable<T> source, Expression<Func<T, object>> keySelector)
        {
            return source.OrderBy(keySelector, "OrderBy");
        }
        public static IOrderedQueryable<T> OrderByDescending<T>(this IQueryable<T> source, Expression<Func<T, object>> keySelector)
        {
            return source.OrderBy(keySelector, "OrderByDescending");
        }
        public static IOrderedQueryable<T> ThenBy<T>(this IOrderedQueryable<T> source, Expression<Func<T, object>> keySelector)
        {
            return source.OrderBy(keySelector, "ThenBy");
        }
        public static IOrderedQueryable<T> ThenByDescending<T>(this IOrderedQueryable<T> source, Expression<Func<T, object>> keySelector)
        {
            return source.OrderBy(keySelector, "ThenByDescending");
        }
        private static IOrderedQueryable<T> OrderBy<T>(this IQueryable<T> source, Expression<Func<T, object>> keySelector, string method)
        {
            var parameter = keySelector.Parameters[0];
            var body = keySelector.Body;
            if (body.NodeType == ExpressionType.Convert)
                body = ((UnaryExpression)body).Operand;
            var selector = Expression.Lambda(body, parameter);
            var methodCall = Expression.Call(
                typeof(Queryable), method, new[] { parameter.Type, body.Type },
                source.Expression, Expression.Quote(selector));
            return (IOrderedQueryable<T>)source.Provider.CreateQuery(methodCall);
        }
    }
    

    One important detail here is that Expression<Func<T, object>> introduces Expression.Convert for value type returning expressions, so it needs to be stripped from the actual lambda body, which is accomplished with the following part of the code:

    var body = keySelector.Body;
    if (body.NodeType == ExpressionType.Convert)
        body = ((UnaryExpression)body).Operand;