0

I was writing a IQueryable friendly replacement for WebGrid and I came to a halt around the part where I have to order the results by column name passed as a string. I managed to achieve my goal, but I don't like the resulting code so I hoped that someone could give me a hint how to improve it.

I wrote an extension for IQueryable for ordering by string based on what I found here, however it did not work at first and was giving me an error that said (more or less) that Int32 cannot be converted to Object (at this point I was testing ordering by column name with a column of ids, that's why the error said Int32). The problematic line:

var mySortExpression = Expression.Lambda<Func<T, object>>(Expression.Property(param, columnName), param);

I've added a conversion based on what can be found at this page, but that didn't work either and I got an error that said that I cannot order by object class. Here is how it looked at this point:

var mySortExpression = Expression.Lambda<Func<T, object>>(Expression.Convert(Expression.Property(param, columnName), type), param);

So I figured out that I need to provide a sortable type in this line:

var mySortExpression = Expression.Lambda<Func<T, type>>(Expression.Property(param, columnName), param);

And rewritten the whole thing as follows:

    public static IQueryable<T> OrderByString<T>(this IQueryable<T> query, string columnName)
    {
        var elementType = typeof(T);
        var param = Expression.Parameter(elementType, "x");

        var prop = elementType.GetProperty(columnName);
        Type type = Nullable.GetUnderlyingType(prop.PropertyType);
        if (type == null)
        {
            type = prop.PropertyType;
        }

        if (type.Equals(typeof(string)))
        {
            var mySortExpression = Expression.Lambda<Func<T, string>>(Expression.Property(param, columnName), param);
            return query.OrderBy(mySortExpression);
        }
        if (type.Equals(typeof(char)))
        {
            var mySortExpression = Expression.Lambda<Func<T, char>>(Expression.Property(param, columnName), param);
            return query.OrderBy(mySortExpression);
        }
        if (type.Equals(typeof(int)))
        {
            var mySortExpression = Expression.Lambda<Func<T, int>>(Expression.Property(param, columnName), param);
            return query.OrderBy(mySortExpression);
        }
        if (type.Equals(typeof(float)) || type.Equals(typeof(double)))
        {
            var mySortExpression = Expression.Lambda<Func<T, double>>(Expression.Property(param, columnName), param);
            return query.OrderBy(mySortExpression);
        }
        // This last part won't work but I left it so that it can compile (all routes need to return value etc.)
        var mySortExpression1 = Expression.Lambda<Func<T, object>>(Expression.Convert(Expression.Property(param, columnName), type), param);

        return query.OrderBy(mySortExpression1);

    }

This actually works, but all those repetitions don't look too good. Is there any way to improve this code?

jahu
  • 4,899
  • 2
  • 35
  • 55
  • 1
    Had the same problem, take Mark Gravell's answer here, it will do all what you need (and more ;) ) : http://stackoverflow.com/a/233505/961526 – Raphaël Althaus Jan 30 '14 at 13:12
  • Many thanks. Somehow I missed that solution and it does indeed do what I wanted (in its core it's actually very similar to what I already found). – jahu Jan 30 '14 at 14:34

1 Answers1

0

As suggested by Raphaël Althaus I solved the problem by using code from https://stackoverflow.com/a/233505/2123652, but I applied a small change to allow it to handle interfaces properly. The result code looks like this:

public static IOrderedQueryable<T> OrderBy<T>(this IQueryable<T> source, string property)
{
    return ApplyOrder<T>(source, property, "OrderBy");
}
public static IOrderedQueryable<T> OrderByDescending<T>(this IQueryable<T> source, string property)
{
    return ApplyOrder<T>(source, property, "OrderByDescending");
}
public static IOrderedQueryable<T> ThenBy<T>(this IOrderedQueryable<T> source, string property)
{
    return ApplyOrder<T>(source, property, "ThenBy");
}
public static IOrderedQueryable<T> ThenByDescending<T>(this IOrderedQueryable<T> source, string property)
{
    return ApplyOrder<T>(source, property, "ThenByDescending");
}
static IOrderedQueryable<T> ApplyOrder<T>(IQueryable<T> source, string property, string methodName) {
    string[] props = property.Split('.');
    Type elementType = source.ElementType;
    Type type = elementType;
    ParameterExpression arg = Expression.Parameter(type, "x");
    Expression expr = arg;
    foreach(string prop in props) {
        // use reflection (not ComponentModel) to mirror LINQ
        PropertyInfo pi = type.GetProperty(prop);
        expr = Expression.Property(expr, pi);
        type = pi.PropertyType;
    }
    Type delegateType = typeof(Func<,>).MakeGenericType(elementType, type);
    LambdaExpression lambda = Expression.Lambda(delegateType, expr, arg);

    object result = typeof(Queryable).GetMethods().Single(
            method => method.Name == methodName
                    && method.IsGenericMethodDefinition
                    && method.GetGenericArguments().Length == 2
                    && method.GetParameters().Length == 2)
            .MakeGenericMethod(elementType, type)
            .Invoke(null, new object[] {source, lambda});
    return (IOrderedQueryable<T>)result;
}

The change in colde involved replacing typeof(T) with source.ElementType.

Community
  • 1
  • 1
jahu
  • 4,899
  • 2
  • 35
  • 55