3

I have an api Get method which accepts sort property as a string. What is a best solution to handle it without using switch statement?

public ActionResult Index(string sortOrder)
{
   var students = from s in db.Students
                  select s;
   switch (sortOrder)
   {
      case "LastName":
         students = students.OrderByDescending(s => s.LastName);
         break;
      case "EnrollmentDate":
         students = students.OrderBy(s => s.EnrollmentDate);
         break;
      case "Birthday":
         students = students.OrderBy(s => s.Birthday);
         break;
      default:
         students = students.OrderBy(s => s.LastName);
         break;
   }
   return View(students.ToList());
}

Following code I found on microsoft's page, but I believe there is should be more elegant way to do it.

mykhailovskyi
  • 680
  • 1
  • 8
  • 19
  • What is difference between these two cases: `case "Enrollment Date":` `case "Enrollment Date":`? I think you can use `Expression.Property` that will be created by input string. – George Alexandria Sep 10 '17 at 22:46
  • Sorry, accidentally put same property name. Just updated. – mykhailovskyi Sep 10 '17 at 22:49
  • @GeorgeAlexandria, could you give me an example how to build `Expression` using the name of property? – mykhailovskyi Sep 10 '17 at 22:52
  • Look at [this](https://stackoverflow.com/questions/45067471/create-expressionfunct-object-from-a-given-type/45067833#45067833). It can be helpful. – George Alexandria Sep 10 '17 at 22:57
  • At first I was thinking of expression too. But now that I think about it, `Expression` can help the other way around. `Expression` helps you to access the selected property when you use a Lamda like you have used inside the `OrderBy` function. But considering that this function is an mvc action, I don't think you can pass a lamda into the function. – Neville Nazerane Sep 10 '17 at 22:58
  • just to confirm? you are using the classic .net framework right? not core? – Neville Nazerane Sep 10 '17 at 23:11
  • @NevilleNazerane, .net core – mykhailovskyi Sep 10 '17 at 23:12
  • 2
    See [How to use a string to create a EF order by expression?](https://stackoverflow.com/questions/39908403/how-to-use-a-string-to-create-a-ef-order-by-expression/39916384#39916384) or similar. – Ivan Stoev Sep 10 '17 at 23:18
  • @IvanStoev, it looks promising. Will try that. Thanks. – mykhailovskyi Sep 10 '17 at 23:25

1 Answers1

3

Use the following method to achieve flexible sorting. The method requires property name and sorting direction parameters.

public Func<IQueryable<T>, IOrderedQueryable<T>> GetOrderByExpression<T>(string propertyName, bool isDescending = false)
    {
        Type typeQueryable = typeof(IQueryable<T>);
        ParameterExpression argQueryable = System.Linq.Expressions.Expression.Parameter(typeQueryable, "p");
        var outerExpression = System.Linq.Expressions.Expression.Lambda(argQueryable, argQueryable);

        var entityType = typeof(T);
        ParameterExpression arg = System.Linq.Expressions.Expression.Parameter(entityType, "x");

        Expression expression = arg;

        PropertyInfo propertyInfo = entityType.GetProperty(propertyName, BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance);
        expression = System.Linq.Expressions.Expression.Property(expression, propertyInfo);

        LambdaExpression lambda = System.Linq.Expressions.Expression.Lambda(expression, arg);            
        string methodName = isDescending ? "OrderByDescending" : "OrderBy";

        MethodCallExpression resultExp = System.Linq.Expressions.Expression.Call(typeof(Queryable),
                                                                                 methodName,
                                                                                 new Type[] { typeof(T), entityType },
                                                                                 outerExpression.Body,
                                                                                 System.Linq.Expressions.Expression.Quote(lambda));

        var finalLambda = System.Linq.Expressions.Expression.Lambda(resultExp, argQueryable);

        return (Func<IQueryable<T>, IOrderedQueryable<T>>)finalLambda.Compile();
    }

Usage:

IQueryable<Student> query = db.Set<Student>();
var orderBy = GetOrderByExpression<Student>(sortOrder, true);

if(orderBy != null){
   query = orderBy(query);
}
Yared
  • 2,206
  • 1
  • 21
  • 30