114

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();
John Demetriou
  • 4,093
  • 6
  • 52
  • 88
Sreedhar
  • 29,307
  • 34
  • 118
  • 188
  • 4
    You might be looking for Dynamic Linq: http://weblogs.asp.net/scottgu/archive/2008/01/07/dynamic-linq-part-1-using-the-linq-dynamic-query-library.aspx – BrokenGlass Sep 01 '11 at 01:12
  • @Nev_Rahd: Tried to clarify the question a bit. Also, `OrderBy` is a Linq feature, and is on `IEnumerable`, not a feature specific to `List`. Feel free to roll the edit back or change it further :) – Merlyn Morgan-Graham Sep 01 '11 at 01:32

12 Answers12

158

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);

Icarus
  • 63,293
  • 14
  • 100
  • 115
  • 11
    This answer is awesome, and much better than the reflection answer. This actually works with other providers like entity framework. – Sam Feb 29 '16 at 04:52
  • 2
    I would up-vote this ten times if I could!!! Where do you learn to write extension method like this??!! – Jach May 31 '16 at 09:19
  • 3
    Should this return an IOrderedQueryable, just like the built-in OrderBy? That way, you could call .ThenBy on it. – Patrick Szalapski Sep 06 '16 at 17:28
  • 1
    How `existingStudents.OrderBy("City",true)` will work, if `existingStudents` is `List`, which not implements `IQueryable`? – STiLeTT Nov 13 '17 at 08:57
  • 2
    Is there any voodoo that can be added to this to make this work for a property from a class that the entity is inheriting from? I usually have a class that all my db classes inherit from that has simple stuff like `createddate` etc. and unfortunately this doesn't work if the sort string/property is from the base class. – americanslon Dec 04 '17 at 21:32
  • 2
    Ha, And what to do if I have Subclass and wanna order by property: List orderbyAddress = existingStudends.OrderBy(c => c.Address).ToList(); ? – alerya Jan 09 '19 at 15:04
  • 4
    This seems to no longer work when using EFCore 3.0, I'm getting a runtime error where it cannot translate the query. – Mildan Oct 04 '19 at 11:28
  • 3
    Yes, @Mildan , This breaks in 3.0 and 3.1 for me as well. with the error ~"cant translate". I use Pomelo for MySQl if that is relevant. The problem is the Expression. IF you hand code the expression it works. So instead of Lambda.Expression() just provide something like: LambdaExpression orderByExp1 = (Expression>)(x => x.Name); – Menace Jan 16 '20 at 21:00
  • 1
    Great answer. In my case, I have multiple sort levels that I want to make configurable, so I changed it to take the command as a parm, so I can do something like: items.OrderBy("OrderByDescending", "ManagerCode").OrderBy("ThenBy", "Region").OrderBy("ThenBy", "Territory"); – James in Indy Feb 26 '20 at 18:47
  • 1
    Brilliant, remember var queryable = myList.AsQueryable(); is using a list – 27k1 Sep 27 '20 at 07:49
  • I did a bit of a hack for my case to support OrderBy and ThenBy automatically: var command = source.Expression.NodeType == ExpressionType.Call ? (desc ? "ThenByDescending" : "ThenBy") : (desc ? "OrderByDescending" : "OrderBy"); – pwhe23 Mar 15 '22 at 00:46
  • For those who need nav props included, this will do the trick: https://stackoverflow.com/a/308863/177416 – Alex Nov 02 '22 at 15:52
149

Here's a possiblity using reflection...

var param = "Address";    
var propertyInfo = typeof(Student).GetProperty(param);    
var orderByAddress = items.OrderBy(x => propertyInfo.GetValue(x, null));
codeConcussion
  • 12,739
  • 8
  • 49
  • 62
  • This is what I was looking for. Was missing the .GetValue portion from my original answer. – tsells Sep 01 '11 at 02:17
  • 4
    But is it true when it comes to Linq expressions interpreted by providers, like Entity Framework (sql server, or other ) ?? – a.boussema Apr 14 '13 at 19:03
  • With this syntax, how can we specify multiple columns in orderby clause ? – VJOY Jul 01 '13 at 07:20
  • 2
    @vijay - use the [`ThenBy` method](http://msdn.microsoft.com/en-us/library/system.linq.queryable.thenby.aspx). – codeConcussion Jul 01 '13 at 20:27
  • 10
    When I try this I get the error: LINQ to Entities does not recognize the method 'System.Object GetValue(System.Object, System.Object[])' method, and this method cannot be translated into a store expression. Does this answer only apply to Linq To SQL? – philreed Jun 18 '14 at 12:51
  • I am also getting above error as mention by philreed, while using above code. – Vimal Patel Feb 11 '15 at 04:56
  • 5
    No error with .AsEnumerable(): var orderByAddress = items.AsEnumerable().OrderBy(x => propertyInfo.GetValue(x, null)); – Caesar Aug 21 '15 at 05:43
  • 1
    How can i dynamically decide to order by asc or desc – Hitesh Modha Oct 14 '15 at 11:18
  • 1
    @Hitesh - Not sure what you mean. Why won't something like this work...`var ordered = isAscending ? items.OrderBy(x => propertyInfo.GetValue(x, null) : items.OrderByDescending(x => propertyInfo.GetValue(x, null));` – codeConcussion Oct 14 '15 at 19:30
  • @codeConcussion creating property is complex, there is simple solution Linq.Dynamic. In which i can pass param like this OrderBy('name desc'). To accomplish this we need to install nuget package. – Hitesh Modha Oct 15 '15 at 05:21
  • @caesar in Linq to Entity Framework, calling .AsEnumerable() didn't solve the problem. Getting error. – Korayem Jan 28 '16 at 14:47
  • Can someone confirm that this still works in .NET Core 3? I am getting "could not be translated error" and this link to docs https://go.microsoft.com/fwlink/?linkid=2101038 – Milan Švehla Oct 28 '19 at 12:04
14

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);
}
Mogsdad
  • 44,709
  • 21
  • 151
  • 275
Ciaran
  • 543
  • 5
  • 14
  • 2
    It seems that other answers were not appropriate for Entity Framework. This is a perfect solution for EF as Linq to Entities does not support GetProperty, GetValue – Bill May 24 '18 at 18:11
  • 1
    This method seems to fail for me in 3.0 and 3.1 (it worked in 2.2). I use Pomelo for MySql so that might be relevant. There is a work around but its ugly. See my comment above. – Menace Jan 16 '20 at 21:06
  • 1
    This worked for me in EF 3.0. However, you should change the following line so that the front-end doesn't need to match case-sensitivity: **var property = type.GetProperty(OrderByProperty, BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance);** – King Arthur the Third Feb 25 '20 at 05:57
  • Is this still optimized for Core 3.1? – Chris Go May 15 '20 at 00:26
  • Need one help. Unble to use with ThenBy – Aԃιƚყα Gυɾαʋ Jul 27 '22 at 12:01
8

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!

Igor Valikovsky
  • 586
  • 5
  • 5
7

Here's something I came up with for dealing with a conditional Descending. You could combine this with other methods of generating the keySelector func dynamically.

    public static IOrderedQueryable<TSource> OrderBy<TSource, TKey>(this IQueryable<TSource> source,
            System.Linq.Expressions.Expression<Func<TSource, TKey>> keySelector,
            System.ComponentModel.ListSortDirection sortOrder
            )
    {
        if (sortOrder == System.ComponentModel.ListSortDirection.Ascending)
            return source.OrderBy(keySelector);
        else
            return source.OrderByDescending(keySelector);
    }

Usage:

//imagine this is some parameter
var direction = System.ComponentModel.ListSortDirection.Ascending;
query = query.OrderBy(ec => ec.MyColumnName, direction);

Notice this allows you to chain this .OrderBy extension with a new parameter onto any IQueryable.

// perhaps passed in as a request of user to change sort order
// var direction = System.ComponentModel.ListSortDirection.Ascending;
query = context.Orders
        .Where(o => o.Status == OrderStatus.Paid)
        .OrderBy(ec => ec.OrderPaidUtc, direction);
AaronLS
  • 37,329
  • 20
  • 143
  • 202
6
   private Func<T, object> GetOrderByExpression<T>(string sortColumn)
    {
        Func<T, object> orderByExpr = null;
        if (!String.IsNullOrEmpty(sortColumn))
        {
            Type sponsorResultType = typeof(T);

            if (sponsorResultType.GetProperties().Any(prop => prop.Name == sortColumn))
            {
                System.Reflection.PropertyInfo pinfo = sponsorResultType.GetProperty(sortColumn);
                orderByExpr = (data => pinfo.GetValue(data, null));
            }
        }
        return orderByExpr;
    }

    public List<T> OrderByDir<T>(IEnumerable<T> source, string dir, Func<T, object> OrderByColumn)
    {
        return dir.ToUpper() == "ASC" ? source.OrderBy(OrderByColumn).ToList() : source.OrderByDescending(OrderByColumn).ToList();``
    }

 // Call the code like below
        var orderByExpression= GetOrderByExpression<SearchResultsType>(sort);

    var data = OrderByDir<SponsorSearchResults>(resultRecords, SortDirectionString, orderByExpression);    
3

This doesn't let you pass a string, as you asked for in your question, but it might still work for you.

The OrderByDescending method takes a Func<TSource, TKey>, so you can rewrite your function this way:

List<Student> QueryStudents<TKey>(Func<Student, TKey> orderBy)
{
    return existingStudents.OrderByDescending(orderBy).ToList();
}

There are other overloads for OrderByDescending as well that take a Expression<Func<TSource, TKey>>, and/or a IComparer<TKey>. You could also look into those and see if they provide you anything of use.

Merlyn Morgan-Graham
  • 58,163
  • 16
  • 128
  • 183
  • This does not work because you do not define the type of TKey. You have to change your to have instead. – Patrick Desjardins Apr 11 '14 at 22:03
  • This was just what worked for me! I wanted a function that would order a list ascending, or descending, depending upon a passed bool value. Your code worked great with a little tweaking! – Joe Gayetty Sep 21 '16 at 12:30
  • LINQ in Action: IEnumerable CustomSort(Func selector, Boolean ascending) { IEnumerable books = SampleData.Books; return ascending ? books.OrderBy(selector) : books.OrderByDescending(selector); } – Leszek P Sep 21 '17 at 12:54
2

The only solution that worked for me was posted here https://gist.github.com/neoGeneva/1878868 by neoGeneva.

I will re-post his code because it works well and I wouldn't want it to be lost in the interwebs!

    public static IQueryable<T> OrderBy<T>(this IQueryable<T> source, string sortExpression)
    {
        if (source == null)
            throw new ArgumentNullException("source", "source is null.");

        if (string.IsNullOrEmpty(sortExpression))
            throw new ArgumentException("sortExpression is null or empty.", "sortExpression");

        var parts = sortExpression.Split(' ');
        var isDescending = false;
        var propertyName = "";
        var tType = typeof(T);

        if (parts.Length > 0 && parts[0] != "")
        {
            propertyName = parts[0];

            if (parts.Length > 1)
            {
                isDescending = parts[1].ToLower().Contains("esc");
            }

            PropertyInfo prop = tType.GetProperty(propertyName);

            if (prop == null)
            {
                throw new ArgumentException(string.Format("No property '{0}' on type '{1}'", propertyName, tType.Name));
            }

            var funcType = typeof(Func<,>)
                .MakeGenericType(tType, prop.PropertyType);

            var lambdaBuilder = typeof(Expression)
                .GetMethods()
                .First(x => x.Name == "Lambda" && x.ContainsGenericParameters && x.GetParameters().Length == 2)
                .MakeGenericMethod(funcType);

            var parameter = Expression.Parameter(tType);
            var propExpress = Expression.Property(parameter, prop);

            var sortLambda = lambdaBuilder
                .Invoke(null, new object[] { propExpress, new ParameterExpression[] { parameter } });

            var sorter = typeof(Queryable)
                .GetMethods()
                .FirstOrDefault(x => x.Name == (isDescending ? "OrderByDescending" : "OrderBy") && x.GetParameters().Length == 2)
                .MakeGenericMethod(new[] { tType, prop.PropertyType });

            return (IQueryable<T>)sorter
                .Invoke(null, new object[] { source, sortLambda });
        }

        return source;
    }
user1477388
  • 20,790
  • 32
  • 144
  • 264
1
  • Add the nugget package Dynamite to your code

  • Add the namespace Dynamite.Extensions Eg : using Dynamite.Extensions;

  • Give Order by query like any SQL query Eg : students.OrderBy(" City DESC, Address").ToList();

Sreeja S J
  • 11
  • 3
1

To extend the response of @Icarus: if you want to sort by two fields I could perform the following function (for one field the response of Icarius works very well).

public static IQueryable<T> OrderByDynamic<T>(this IQueryable<T> q, string SortField1, string SortField2, bool Ascending)
        {
            var param = Expression.Parameter(typeof(T), "p");
            var body = GetBodyExp(SortField1, SortField2, param);
            var exp = Expression.Lambda(body, param);

            string method = Ascending ? "OrderBy" : "OrderByDescending";
            Type[] types = new Type[] { q.ElementType, exp.Body.Type };
            var mce = Expression.Call(typeof(Queryable), method, types, q.Expression, exp);
            return q.Provider.CreateQuery<T>(mce);
        }

This is the function that the body returns for the lambda expression, it works with string and int, but it is enough to add more types to make it work according to the need of each programmer

public static NewExpression GetBodyExp(string field1, string field2, ParameterExpression Parametro)
        {    
            // SE OBTIENE LOS NOMBRES DE LOS TIPOS DE VARIABLE 
            string TypeName1 = Expression.Property(Parametro, field1).Type.Name;
            string TypeName2 = Expression.Property(Parametro, field2).Type.Name;

            // SE DECLARA EL TIPO ANONIMO SEGUN LOS TIPOS DE VARIABLES
            Type TypeAnonymous = null;
            if (TypeName1 == "String")
            {
                string var1 = "0";
                if (TypeName2 == "Int32")
                {
                    int var2 = 0;
                    var example = new { var1, var2 };
                    TypeAnonymous = example.GetType();
                }

                if (TypeName2 == "String")
                {
                    string var2 = "0";
                    var example = new { var1, var2 };
                    TypeAnonymous = example.GetType();
                }    
            }    

            if (TypeName1 == "Int32")
            {
                int var1 = 0;
                if (TypeName2 == "Int32")
                {
                    string var2 = "0";
                    var example = new { var1, var2 };
                    TypeAnonymous = example.GetType();
                }

                if (TypeName2 == "String")
                {
                    string var2 = "0";
                    var example = new { var1, var2 };
                    TypeAnonymous = example.GetType();
                }    
            }

            //se declaran los TIPOS NECESARIOS PARA GENERAR EL BODY DE LA EXPRESION LAMBDA
            MemberExpression[] args = new[] { Expression.PropertyOrField(Parametro, field1), Expression.PropertyOrField(Parametro, field2) };
            ConstructorInfo CInfo = TypeAnonymous.GetConstructors()[0];
            IEnumerable<MemberInfo> a = TypeAnonymous.GetMembers().Where(m => m.MemberType == MemberTypes.Property);

            //BODY 
            NewExpression body = Expression.New(CInfo, args, TypeAnonymous.GetMembers().Where(m => m.MemberType == MemberTypes.Property));

            return body;
        }

to use it the following is done

IQueryable<MyClass> IqMyClass= context.MyClass.AsQueryable();
List<MyClass> ListMyClass= IqMyClass.OrderByDynamic("UserName", "IdMyClass", true).ToList();

if there is a better way to do this, it would be great if they share it

I managed to solve it thanks to: How can I make a Multiple property lambda expression with Linq

1

New Answer : this is a more complete answer that supports multiple columns for order by like SQL. Example : .OrderBy("FirstName,Age DESC") :

namespace Utility;
 public static class QueryExtension
{
        public static IQueryable<TEntity> OrderBy<TEntity>(this IQueryable<TEntity> source, string orderByProperty, bool desc, bool isThenBy = false)
        {
            string command = isThenBy ? (desc ? "ThenByDescending" : "ThenBy") : (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);
        }

        public static IQueryable<TEntity> OrderBy<TEntity>(this IQueryable<TEntity> source, string sqlOrderByList)
        {
            var ordebyItems = sqlOrderByList.Trim().Split(',');
            IQueryable<TEntity> result = source;
            bool useThenBy = false;
            foreach (var item in ordebyItems)
            {
                var splt = item.Trim().Split(' ');
                result = result.OrderBy(splt[0].Trim(), (splt.Length > 1 && splt[1].Trim().ToLower() == "desc"), useThenBy);
                if (useThenBy)
                    useThenBy = true;
            }
            return result;
        }
}

The second function iterates over orderby columns and uses the first one.

Use it like this :

using Utility; 
...
public void MyMethod()
{
    var query = _dbContext.Person.AsQueryable();
    query.OrderBy("FirstName,Age DESC");

}
nAviD
  • 2,784
  • 1
  • 33
  • 54
-3

I'm way late to the party but none of these solutions worked for me. I was eager to try System.Linq.Dynamic, but I couldn't find that on Nuget, maybe depreciated? Either way...

Here is a solutions I came up with. I needed to dynamically use a mixture of OrderBy, OrderByDescending and OrderBy > ThenBy.

I simply created an extension method for my list object, a bit hacky I know... I wouldn't recommend this if it were something I was doing a lot of, but it's good for a one off.

List<Employee> Employees = GetAllEmployees();

foreach(Employee oEmployee in Employees.ApplyDynamicSort(eEmployeeSort))
{
    //do stuff
}

public static IOrderedEnumerable<Employee> ApplyDynamicSort(this List<Employee> lEmployees, Enums.EmployeeSort eEmployeeSort)
{
    switch (eEmployeeSort)
    {
        case Enums.EmployeeSort.Name_ASC:
            return lEmployees.OrderBy(x => x.Name);
        case Enums.EmployeeSort.Name_DESC:
            return lEmployees.OrderByDescending(x => x.Name);
        case Enums.EmployeeSort.Department_ASC_Salary_DESC:
            return lEmployees.OrderBy(x => x.Department).ThenByDescending(y => y.Salary);
        default:
            return lEmployees.OrderBy(x => x.Name);
    }
}
NickAndrews
  • 496
  • 2
  • 9