75

I want to do this:

var orderBy = "Nome, Cognome desc";

var timb = time.Timbratures.Include("Anagrafica_Dipendente")
    .Where(p => p.CodDipendente == 1);

if(orderBy != "")
    timb = timb.OrderBy(orderBy);

Is there an OrderBy overload available that accepts a string parameter?

user247702
  • 23,641
  • 15
  • 110
  • 157
Luca Romagnoli
  • 12,145
  • 30
  • 95
  • 157
  • 1
    Possible duplicate of [How do I specify the Linq OrderBy argument dynamically?](http://stackoverflow.com/questions/7265186/how-do-i-specify-the-linq-orderby-argument-dynamically) – Korayem Jan 28 '16 at 14:43
  • Here is good answer. Without any third libraries. https://stackoverflow.com/a/233505/714828 – Alexander Demko Jul 04 '17 at 14:12
  • for entity framework, see [this](https://stackoverflow.com/a/51480781/2803565) answer (using `EF.property`) – S.Serpooshan Jul 23 '18 at 14:10

11 Answers11

85

If you are using plain LINQ-to-objects and don't want to take a dependency on an external library it is not hard to achieve what you want.

The OrderBy() clause accepts a Func<TSource, TKey> that gets a sort key from a source element. You can define the function outside the OrderBy() clause:

Func<Item, Object> orderByFunc = null;

You can then assign it to different values depending on the sort criteria:

if (sortOrder == SortOrder.SortByName)
  orderByFunc = item => item.Name;
else if (sortOrder == SortOrder.SortByRank)
  orderByFunc = item => item.Rank;

Then you can sort:

var sortedItems = items.OrderBy(orderByFunc);

This example assumes that the source type is Item that have properties Name and Rank.

Note that in this example TKey is Object to not constrain the property types that can be sorted on. If the func returns a value type (like Int32) it will get boxed when sorting and that is somewhat inefficient. If you can constrain TKey to a specific value type you can work around this problem.

Martin Liversage
  • 104,481
  • 22
  • 209
  • 256
  • 4
    Clean, great answer, the TKey was what was confusing me and I assumed it was defined higher up the chain and couldn't be stamped so easily. I've been using the DynLinq resolution for some time. Thanks for this. – Dave Jellison Dec 08 '12 at 22:12
  • If I need the first dynamic order to be descending, and the second one to be ascending - is there a trick for that? – Yuval A. Jul 23 '14 at 15:43
  • 2
    @YuvalA.: If one of the properties is numeric (this includes `DateTime.Ticks`) you can negate the value to sort in the opposite order. Otherwise you can use `OrderBy` or `OrderByDescending` conditionally: `var sortedItems = reverse ? items.OrderByDescending(orderByFunc) : items.OrderBy(orderByFunc)`. – Martin Liversage Jul 25 '14 at 11:07
  • for each property I need to add an if condition? – juanora Oct 23 '18 at 09:59
53

Absolutely. You can use the LINQ Dynamic Query Library, found on Scott Guthrie's blog. There's also an updated version available on CodePlex.

It lets you create OrderBy clauses, Where clauses, and just about everything else by passing in string parameters. It works great for creating generic code for sorting/filtering grids, etc.

var result = data
    .Where(/* ... */)
    .Select(/* ... */)
    .OrderBy("Foo asc");

var query = DbContext.Data
    .Where(/* ... */)
    .Select(/* ... */)
    .OrderBy("Foo ascending");
Mike Mooney
  • 11,729
  • 3
  • 36
  • 42
  • 5
    Is there any security issues with this? As in LINQ injection, would it be unsafe to allow user input into the `OrderBy` field? e.g `.OrderBy("UserInput ascending").` – jthomperoo Jun 09 '17 at 14:55
  • This is available as a Nuget package for those interested – ElliotSchmelliot Feb 27 '18 at 19:39
  • 2
    @jthomperoo, you should always validate the user input here. It is trivial. – Dima Dec 05 '18 at 20:50
  • @jthomperoo: Any unsanitized string value injected into SQL is a problem, no matter where you inject it. I would reasonably expect that DynamicLinq sanitizes the input, but I wouldn't blindly trust it before I put it into production. – Flater Feb 16 '21 at 11:43
37

Another solution from codeConcussion (https://stackoverflow.com/a/7265394/2793768)

var param = "Address";    
var pi = typeof(Student).GetProperty(param);    
var orderByAddress = items.OrderBy(x => pi.GetValue(x, null));
Community
  • 1
  • 1
Chinh Phan
  • 1,459
  • 19
  • 22
  • 10
    This doesnt work in Linq to Entity Framework as it throws this 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.` – Korayem Jan 28 '16 at 14:41
  • with that error, could u try: var orderByAddress = items.AsEnumerable().OrderBy(x => propertyInfo.GetValue(x, null)) – Chinh Phan Feb 16 '16 at 06:39
  • 5
    Not a good thing if he wants to paginate the results. Such pagination would come after the OrderBy with Skip() and Take()... – Abílio Esteves Oct 31 '16 at 19:59
  • You can not insert an unexpected condition in Linq to Entities IQueriable. You only can fetch the data to be able to work on it with all C# abilities, which is not performant. – Bamdad Jan 10 '21 at 07:50
33

The simplest & the best solution:

mylist.OrderBy(s => s.GetType().GetProperty("PropertyName").GetValue(s));
Ramy Yousef
  • 2,982
  • 2
  • 25
  • 24
27

You don't need an external library for this. The below code works for LINQ to SQL/entities.

    /// <summary>
    /// Sorts the elements of a sequence according to a key and the sort order.
    /// </summary>
    /// <typeparam name="TSource">The type of the elements of <paramref name="query" />.</typeparam>
    /// <param name="query">A sequence of values to order.</param>
    /// <param name="key">Name of the property of <see cref="TSource"/> by which to sort the elements.</param>
    /// <param name="ascending">True for ascending order, false for descending order.</param>
    /// <returns>An <see cref="T:System.Linq.IOrderedQueryable`1" /> whose elements are sorted according to a key and sort order.</returns>
    public static IQueryable<TSource> OrderBy<TSource>(this IQueryable<TSource> query, string key, bool ascending = true)
    {
        if (string.IsNullOrWhiteSpace(key))
        {
            return query;
        }

        var lambda = (dynamic)CreateExpression(typeof(TSource), key);

        return ascending 
            ? Queryable.OrderBy(query, lambda) 
            : Queryable.OrderByDescending(query, lambda);
    }

    private static LambdaExpression CreateExpression(Type type, string propertyName)
    {
        var param = Expression.Parameter(type, "x");

        Expression body = param;
        foreach (var member in propertyName.Split('.'))
        {
            body = Expression.PropertyOrField(body, member);
        }

        return Expression.Lambda(body, param);
    }

(CreateExpression copied from https://stackoverflow.com/a/16208620/111438)

Community
  • 1
  • 1
niaher
  • 9,460
  • 7
  • 67
  • 86
  • 1
    I confirm this works, but it doesn't work when TSource is private class – Liero Feb 21 '19 at 15:48
  • Out of all the different solutions on the Internet, this one was the one that helped me most. There are a few things that are novel about this solution by comparison to others: 1) using the dynamic type to cast LambdaExpression basically works as shorthand for calling MethodInfo.Invoke and bypassing type constraints. 2) calling Queryable.OrderBy() as static instead of as an extension method seems to help ensure the proper overload is called in spite of using a dynamic type. 3) the foreach propertyName.Split loop is great for dynamically allowing nesting of associations. – scradam Oct 25 '22 at 17:53
11

I did so:

using System.Linq.Expressions;

namespace System.Linq
{
    public static class LinqExtensions
    {

        public static IOrderedQueryable<TSource> OrderBy<TSource>(this IQueryable<TSource> source, string field, string dir = "asc")
        {
            // parametro => expressão
            var parametro = Expression.Parameter(typeof(TSource), "r");
            var expressao = Expression.Property(parametro, field);
            var lambda = Expression.Lambda(expressao, parametro); // r => r.AlgumaCoisa
            var tipo = typeof(TSource).GetProperty(field).PropertyType;

            var nome = "OrderBy";
            if (string.Equals(dir, "desc", StringComparison.InvariantCultureIgnoreCase))
            {
                nome = "OrderByDescending";
            }
            var metodo = typeof(Queryable).GetMethods().First(m => m.Name == nome && m.GetParameters().Length == 2);
            var metodoGenerico = metodo.MakeGenericMethod(new[] { typeof(TSource), tipo });
            return metodoGenerico.Invoke(source, new object[] { source, lambda }) as IOrderedQueryable<TSource>;

        }

        public static IOrderedQueryable<TSource> ThenBy<TSource>(this IOrderedQueryable<TSource> source, string field, string dir = "asc")
        {
            var parametro = Expression.Parameter(typeof(TSource), "r");
            var expressao = Expression.Property(parametro, field);
            var lambda = Expression.Lambda<Func<TSource, string>>(expressao, parametro); // r => r.AlgumaCoisa
            var tipo = typeof(TSource).GetProperty(field).PropertyType;

            var nome = "ThenBy";
            if (string.Equals(dir, "desc", StringComparison.InvariantCultureIgnoreCase))
            {
                nome = "ThenByDescending";
            }

            var metodo = typeof(Queryable).GetMethods().First(m => m.Name == nome && m.GetParameters().Length == 2);
            var metodoGenerico = metodo.MakeGenericMethod(new[] { typeof(TSource), tipo });
            return metodoGenerico.Invoke(source, new object[] { source, lambda }) as IOrderedQueryable<TSource>;
        }

    }
}

Use :

example.OrderBy("Nome", "desc").ThenBy("other")

Work like:

example.OrderByDescending(r => r.Nome).ThenBy(r => r.other)
Gustavo Rossi Muller
  • 1,062
  • 14
  • 18
7

Look at this blog here. It describes a way to do this, by defining an EntitySorter<T>.

It allows you to pass in an IEntitySorter<T> into your service methods and use it like this:

public static Person[] GetAllPersons(IEntitySorter<Person> sorter)
{
    using (var db = ContextFactory.CreateContext())
    {
        IOrderedQueryable<Person> sortedList = sorter.Sort(db.Persons);

        return sortedList.ToArray();
    }
}

And you can create an EntitiySorter like this:

IEntitySorter<Person> sorter = EntitySorter<Person>
    .OrderBy(p => p.Name)
    .ThenByDescending(p => p.Id);

Or like this:

var sorter = EntitySorter<Person>
     .OrderByDescending("Address.City")
     .ThenBy("Id");
Steven
  • 166,672
  • 24
  • 332
  • 435
  • Great answer - encapsulating the sorting in its own class is a reusable and flexible solution – Alex Feb 26 '16 at 09:41
5

You need to use the LINQ Dynamic Query Library in order to pass parameters at runtime,

This will allow linq statements like

string orderedBy = "Description";
var query = (from p in products
            orderby(orderedBy)
            select p);
Nicholas Murray
  • 13,305
  • 14
  • 65
  • 84
0

If your columnName is in a variable col, then

string col="name";

list.OrderBy(x=>x[col])
RISHU GUPTA
  • 493
  • 6
  • 8
0

As what Martin Liversage said, you can define a Func<>before you pass it to OrderBy method, but I found an interesting way to do that.


You can define a dictionary from string to Func<> like this :

Dictionary<string, Func<Item, object>> SortParameters = new Dictionary<string, Func<Item, object>>()
{
    {"Rank", x => x.Rank}
};

And use it like this :

yourList.OrderBy(SortParameters["Rank"]);

In this case you can dynamically sort by string.

Sajed
  • 1,797
  • 2
  • 7
  • 21
-3

In one answer above:

The simplest & the best solution:

mylist.OrderBy(s => s.GetType().GetProperty("PropertyName").GetValue(s));

There is an syntax error, ,null must be added:

mylist.OrderBy(s => s.GetType().GetProperty("PropertyName").GetValue(s,null));
Eiko
  • 25,601
  • 15
  • 56
  • 71