9

I am using .NET 4.51, EF 6

I make a number of calls to my repository layer where I need to do some basic ordering on a single field in either ascending or descending order such as:

enter image description here

The result of GetAllList() is a List<T>. Now unfortunately the Id field I have to sort by is not always called Id nor is the Text field. They can be other things such as MyId, SomeTextField and so on.

So I was wondering if there was a way I could do the OrderBy() and OrderByDescending() clauses by supplying a string for the field name something like:

_Repository.GetAllList().OrderBy(r => r."SomeTextField")

In this way I could move all this code to a common method.

Any pointers greatly appreciated.

Brendan Green
  • 10,893
  • 5
  • 40
  • 68
TheEdge
  • 7,981
  • 13
  • 55
  • 117
  • 1
    the only way you'd be able to do exactly what you want with with reflection. You should be able to use a `Func` and accomplish the same thing without using strings though, like `LINQ` does – Jonesopolis Aug 18 '15 at 00:40
  • You can if the object is a DataRow() from a DataTable. – jdweng Aug 18 '15 at 00:40

4 Answers4

19

This will work:

public static class LinqExtensions 
{
    private static PropertyInfo GetPropertyInfo(Type objType, string name)
    {
        var properties = objType.GetProperties();
        var matchedProperty = properties.FirstOrDefault (p => p.Name == name);
        if (matchedProperty == null)
            throw new ArgumentException("name");

        return matchedProperty;
    }
    private static LambdaExpression GetOrderExpression(Type objType, PropertyInfo pi)
    {
        var paramExpr = Expression.Parameter(objType);
        var propAccess = Expression.PropertyOrField(paramExpr, pi.Name);
        var expr = Expression.Lambda(propAccess, paramExpr);
        return expr;
    }

    public static IEnumerable<T> OrderBy<T>(this IEnumerable<T> query, string name)
    {
        var propInfo = GetPropertyInfo(typeof(T), name);
        var expr = GetOrderExpression(typeof(T), propInfo);

        var method = typeof(Enumerable).GetMethods().FirstOrDefault(m => m.Name == "OrderBy" && m.GetParameters().Length == 2);
        var genericMethod = method.MakeGenericMethod(typeof(T), propInfo.PropertyType);     
        return (IEnumerable<T>) genericMethod.Invoke(null, new object[] { query, expr.Compile() });
    }

    public static IQueryable<T> OrderBy<T>(this IQueryable<T> query, string name)
    {
        var propInfo = GetPropertyInfo(typeof(T), name);
        var expr = GetOrderExpression(typeof(T), propInfo);

        var method = typeof(Queryable).GetMethods().FirstOrDefault(m => m.Name == "OrderBy" && m.GetParameters().Length == 2);
        var genericMethod = method.MakeGenericMethod(typeof(T), propInfo.PropertyType);     
        return (IQueryable<T>) genericMethod.Invoke(null, new object[] { query, expr });
    }
}

Testing:

var r = new List<temp> { 
    new temp { a = 5 }, 
    new temp { a = 1 }, 
    new temp { a = 15 }
}.OrderBy("a");

Gives the correct result (1, 5, 15) - and will provide lazy execution for your use with EF

You will need to implement the overloads if needed.

Rob
  • 25,569
  • 15
  • 73
  • 87
6

Does it have to be a string? Why not just make a method that takes a Func key selector as a parameter.

public List<T> GetAllListOrdered<T,TProp>(SimpleOrderingDirectionEnum direction, Func<T,TProp> keySelector)
{
    return direction == SimpleOrderingDirectionEnum.Ascending ? _Repository.GetAllList().OrderBy(keySelector).ToList() : _Repository.GetAllList().OrderByDescending(keySelector).ToList();
}

Then call it like

Func<ObjectToSortType, ObjectPropertyToSortBy> keySelector = r => r.Id;
GetAllListOrdered(SimpleOrderingDirectionEnum.Ascending, keySelector);
nmaait
  • 1,725
  • 1
  • 12
  • 14
  • 1
    This will not translate to sql order by, if we use linq to entities, because linq to entities requires the expression tree for it. Use `Expression>` instead. – Taher Rahgooy Aug 18 '15 at 07:28
2

If the Rob's answer is not enough for you. Try Linq Dynamic. http://dynamiclinq.azurewebsites.net/

using System.Linq.Dynamic; //Import the Dynamic LINQ library

//The standard way, which requires compile-time knowledge
//of the data model
var result = myQuery
    .Where(x => x.Field1 == "SomeValue")
    .Select(x => new { x.Field1, x.Field2 });

//The Dynamic LINQ way, which lets you do the same thing
//without knowing the data model before hand
var result = myQuery
    .Where("Field1=\"SomeValue\"")
    .Select("new (Field1, Field2)");
Fabio Luz
  • 11,534
  • 1
  • 22
  • 40
  • I had considered this, but wanted to do something without pulling in yet another library. I did however end up utilising this in my preferred solution, – TheEdge Aug 18 '15 at 01:25
1

Thanks to all. Rob, your solution was pretty close to what I ended up with.

Based on your insights I did some more searching and came across Marc Gravel's answer here Dynamic LINQ OrderBy on IEnumerable<T> (second post).

It added dynamic's as an additional bonus.

Community
  • 1
  • 1
TheEdge
  • 7,981
  • 13
  • 55
  • 117
  • 1
    Or just use https://stackoverflow.com/a/15389224 There's a System.Linq.Dynamic nuget library that has .OrderBy(string ordering) So you can use it like: list.OrderBy("firstName asc") – Mitch Oct 04 '17 at 16:16