How do I specify the Linq OrderBy argument dynamically? [duplicate]

How do I specify the argument passed to orderby using a value I take as a parameter?

Ex:

List<Student> existingStudends = new List<Student>{ new Student {...}, new Student {...}}

Currently implementation:

List<Student> orderbyAddress = existingStudends.OrderBy(c => c.Address).ToList();

Instead of c.Address, how can I take that as a parameter?

Example

 string param = "City";
 List<Student> orderbyAddress = existingStudends.OrderByDescending(c => param).ToList();

Solution 1:

You can use a little bit of reflection to construct the expression tree as follows (this is an extension method):

public static IQueryable<TEntity> OrderBy<TEntity>(this IQueryable<TEntity> source, string orderByProperty,
                          bool desc) 
{
     string command = desc ? "OrderByDescending" : "OrderBy";
     var type = typeof(TEntity);
     var property = type.GetProperty(orderByProperty);
     var parameter = Expression.Parameter(type, "p");
     var propertyAccess = Expression.MakeMemberAccess(parameter, property);
     var orderByExpression = Expression.Lambda(propertyAccess, parameter);
     var resultExpression = Expression.Call(typeof(Queryable), command, new Type[] { type, property.PropertyType },
                                   source.Expression, Expression.Quote(orderByExpression));
     return source.Provider.CreateQuery<TEntity>(resultExpression);
}

orderByProperty is the Property name you want to order by and if pass true as parameter for desc, will sort in descending order; otherwise, will sort in ascending order.

Now you should be able to do existingStudents.OrderBy("City",true); or existingStudents.OrderBy("City",false);

Solution 2:

Here's a possiblity using reflection...

var param = "Address";    
var propertyInfo = typeof(Student).GetProperty(param);    
var orderByAddress = items.OrderBy(x => propertyInfo.GetValue(x, null));

Solution 3:

To expand on the answer by @Icarus: if you want the return type of the extension method to be an IOrderedQueryable instead of an IQueryable, you can simply cast the result as follows:

public static IOrderedQueryable<TEntity> OrderBy<TEntity>(this IQueryable<TEntity> source, string orderByProperty, bool desc)
{
    string command = desc ? "OrderByDescending" : "OrderBy";
    var type = typeof(TEntity);
    var property = type.GetProperty(orderByProperty);
    var parameter = Expression.Parameter(type, "p");
    var propertyAccess = Expression.MakeMemberAccess(parameter, property);
    var orderByExpression = Expression.Lambda(propertyAccess, parameter);
    var resultExpression = Expression.Call(typeof(Queryable), command, new Type[] { type, property.PropertyType },
        source.Expression, Expression.Quote(orderByExpression));
    return (IOrderedQueryable<TEntity>)source.Provider.CreateQuery<TEntity>(resultExpression);
}

Solution 4:

1) Install System.Linq.Dynamic

2) Add the following code

public static class OrderUtils
{
    public static string ToStringForOrdering<T, TKey>(this Expression<Func<T, TKey>> expression, bool isDesc = false)
    {
        var str = expression.Body.ToString();
        var param = expression.Parameters.First().Name;
        str = str.Replace("Convert(", "(").Replace(param + ".", "");
        return str + (isDesc ? " descending" : "");
    }
}

3) Write your switch for selecting of Lambda function

public static class SortHelper
{
    public static Expression<Func<UserApp, object>> UserApp(string orderProperty)
    {
        orderProperty = orderProperty?.ToLowerInvariant();
        switch (orderProperty)
        {
            case "firstname":
                return x => x.PersonalInfo.FirstName;
            case "lastname":
                return x => x.PersonalInfo.LastName;
            case "fullname":
                return x => x.PersonalInfo.FirstName + x.PersonalInfo.LastName;
            case "email":
                return x => x.Email;

        }
    }
}

4) Use your helpers

Dbset.OrderBy(SortHelper.UserApp("firstname").ToStringForOrdering())

5) You can use it with pagging (PagedList)

public virtual  IPagedList<T> GetPage<TOrder>(Page page, Expression<Func<T, bool>> where, Expression<Func<T, TOrder>> order, bool isDesc = false,
      params Expression<Func<T, object>>[] includes)
    {
        var orderedQueryable = Dbset.OrderBy(order.ToStringForOrdering(isDesc));
        var query = orderedQueryable.Where(where).GetPage(page);
        query = AppendIncludes(query, includes);

        var results = query.ToList();
        var total =  Dbset.Count(where);

        return new StaticPagedList<T>(results, page.PageNumber, page.PageSize, total);
    }

Explanation

System.Linq.Dynamic allows us to set string value in OrderBy method. But inside this extension the string will be parsed to Lambda. So I thought it would work if we will parse Lambda to string and give it to OrderBy method. And it works!