C# - code to order by a property using the property name as a string [duplicate]

Solution 1:

I would offer this alternative to what everyone else has posted.

System.Reflection.PropertyInfo prop = typeof(YourType).GetProperty("PropertyName");

query = query.OrderBy(x => prop.GetValue(x, null));

This avoids repeated calls to the reflection API for obtaining the property. Now the only repeated call is obtaining the value.

However

I would advocate using a PropertyDescriptor instead, as this will allow for custom TypeDescriptors to be assigned to your type, making it possible to have lightweight operations for retrieving properties and values. In the absence of a custom descriptor it will fall back to reflection anyhow.

PropertyDescriptor prop = TypeDescriptor.GetProperties(typeof(YourType)).Find("PropertyName");

query = query.OrderBy(x => prop.GetValue(x));

As for speeding it up, check out Marc Gravel's HyperDescriptor project on CodeProject. I've used this with great success; it's a life saver for high-performance data binding and dynamic property operations on business objects.

Solution 2:

I'm a little late to the party, however, I hope this can be of some help.

The problem with using reflection is that the resulting Expression Tree will almost certainly not be supported by any Linq providers other than the internal .Net provider. This is fine for internal collections, however this will not work where the sorting is to be done at source (be that SQL, MongoDb, etc.) prior to pagination.

The code sample below provides IQueryable extention methods for OrderBy and OrderByDescending, and can be used like so:

query = query.OrderBy("ProductId");

Extension Method:

public static class IQueryableExtensions 
{
    public static IOrderedQueryable<T> OrderBy<T>(this IQueryable<T> source, string propertyName)
    {
        return source.OrderBy(ToLambda<T>(propertyName));
    }

    public static IOrderedQueryable<T> OrderByDescending<T>(this IQueryable<T> source, string propertyName)
    {
        return source.OrderByDescending(ToLambda<T>(propertyName));
    }

    private static Expression<Func<T, object>> ToLambda<T>(string propertyName)
    {
        var parameter = Expression.Parameter(typeof(T));
        var property = Expression.Property(parameter, propertyName);
        var propAsObject = Expression.Convert(property, typeof(object));

        return Expression.Lambda<Func<T, object>>(propAsObject, parameter);            
    }
}

Regards, Mark.

Solution 3:

I liked the answer from @Mark Powell, but as @ShuberFu said, it gives the error LINQ to Entities only supports casting EDM primitive or enumeration types.

Removing var propAsObject = Expression.Convert(property, typeof(object)); didn't work with properties that were value types, such as integer, as it wouldn't implicitly box the int to object.

Using Ideas from Kristofer Andersson and Marc Gravell I found a way to construct the Queryable function using the property name and have it still work with Entity Framework. I also included an optional IComparer parameter. Caution: The IComparer parameter does not work with Entity Framework and should be left out if using Linq to Sql.

The following works with Entity Framework and Linq to Sql:

query = query.OrderBy("ProductId");

And @Simon Scheurer this also works:

query = query.OrderBy("ProductCategory.CategoryId");

And if you are not using Entity Framework or Linq to Sql, this works:

query = query.OrderBy("ProductCategory", comparer);

Here is the code:

public static class IQueryableExtensions 
{    
public static IOrderedQueryable<T> OrderBy<T>(this IQueryable<T> query, string propertyName, IComparer<object> comparer = null)
{
    return CallOrderedQueryable(query, "OrderBy", propertyName, comparer);
}

public static IOrderedQueryable<T> OrderByDescending<T>(this IQueryable<T> query, string propertyName, IComparer<object> comparer = null)
{
    return CallOrderedQueryable(query, "OrderByDescending", propertyName, comparer);
}

public static IOrderedQueryable<T> ThenBy<T>(this IOrderedQueryable<T> query, string propertyName, IComparer<object> comparer = null)
{
    return CallOrderedQueryable(query, "ThenBy", propertyName, comparer);
}

public static IOrderedQueryable<T> ThenByDescending<T>(this IOrderedQueryable<T> query, string propertyName, IComparer<object> comparer = null)
{
    return CallOrderedQueryable(query, "ThenByDescending", propertyName, comparer);
}

/// <summary>
/// Builds the Queryable functions using a TSource property name.
/// </summary>
public static IOrderedQueryable<T> CallOrderedQueryable<T>(this IQueryable<T> query, string methodName, string propertyName,
        IComparer<object> comparer = null)
{
    var param = Expression.Parameter(typeof(T), "x");

    var body = propertyName.Split('.').Aggregate<string, Expression>(param, Expression.PropertyOrField);

    return comparer != null
        ? (IOrderedQueryable<T>)query.Provider.CreateQuery(
            Expression.Call(
                typeof(Queryable),
                methodName,
                new[] { typeof(T), body.Type },
                query.Expression,
                Expression.Lambda(body, param),
                Expression.Constant(comparer)
            )
        )
        : (IOrderedQueryable<T>)query.Provider.CreateQuery(
            Expression.Call(
                typeof(Queryable),
                methodName,
                new[] { typeof(T), body.Type },
                query.Expression,
                Expression.Lambda(body, param)
            )
        );
}
}