linq Order By for a List(Of myObjects)

13,714

Solution 1

VB

Module OrderByExtensions
  <System.Runtime.CompilerServices.Extension()> _
  Public Function OrderByPropertyName(Of T)(ByVal e As IEnumerable(Of T), ByVal propertyName As String) As IOrderedEnumerable(Of T)
    Dim itemType = GetType(T)
    Dim prop = itemType.GetProperty(propertyName)
    If prop Is Nothing Then Throw New ArgumentException("Object does not have the specified property")
    Dim propType = prop.PropertyType
    Dim funcType = GetType(Func(Of ,)).MakeGenericType(itemType, propType)
    Dim parameter = Expression.Parameter(itemType, "item")
    Dim exp = Expression.Lambda(funcType, Expression.MakeMemberAccess(parameter, prop), parameter)
    Dim params = New Object() {e, exp.Compile()}
    Return DirectCast(GetType(OrderByExtensions).GetMethod("InvokeOrderBy", Reflection.BindingFlags.Static Or Reflection.BindingFlags.NonPublic).MakeGenericMethod(itemType, propType).Invoke(Nothing, params), IOrderedEnumerable(Of T))
  End Function
  Private Function InvokeOrderBy(Of T, U)(ByVal e As IEnumerable(Of T), ByVal f As Func(Of T, U)) As IOrderedEnumerable(Of T)
    Return Enumerable.OrderBy(e, f)
  End Function
End Module

C#

public static class OrderByExtensions
{
  public static IOrderedEnumerable<T> OrderByPropertyName<T>(this IEnumerable<T> e, string name)
  {
    var itemType = typeof(T);
    var prop = itemType.GetProperty(name);
    if (prop == null) throw new ArgumentException("Object does not have the specified property");
    var propType = prop.PropertyType;
    var funcType = typeof(Func<,>).MakeGenericType(itemType, propType);
    var parameter = Expression.Parameter(itemType, "item");
    var memberAccess = Expression.MakeMemberAccess(parameter, prop);
    var expression = Expression.Lambda(funcType, memberAccess, parameter);
    var x = typeof(OrderByExtensions).GetMethod("InvokeOrderBy", BindingFlags.Static | BindingFlags.NonPublic);
    return (IOrderedEnumerable<T>)x.MakeGenericMethod(itemType, propType).Invoke(null, new object[] { e, expression.Compile() });
  }
  static IOrderedEnumerable<T> InvokeOrderBy<T, U>(IEnumerable<T> e, Func<T, U> f)
  {
    return e.OrderBy(f);
  }
}

Solution 2

Pass the sort column as an Function.

So it would be

public SomeList Foo(Function<Foo, bool> sortFunction, int skip, int PageSize)
{
   return returnReports.OrderBy(sortFunction).Skip(skip).Take(PageSize).ToList();
}

Call it like this

SomeList(f => f.Bar, 5, 10);

Solution 3

if you're working with a database as a source of data then you can use Dynamic LINQ project that allows you to specify parameters to the Where clause as a string.

If you're working with "Linq to objects" than you'll need to create the lambda function that is passed as an argument dynamically. You can do this by using the "Expression.Xyz" methods to build the expression tree and then using the "Compile" method that turns the expression tree into a callable delegate (of type Func<>) that you can use as an argument to Where. An example how to construct the expression tree can be found in another SO thread here.

Solution 4

You can't do it (easily) if you're just passed a string. You could have a map from String to Func<IEnumerable<Report>, IEnumerable<Report>>, e.g. (in C#)

// Horrible type. Ick.
private static readonly
    Dictionary<string, Func<IEnumerable<Report>,IEnumerable<Report>>> 
    Orderings = 
    new Dictionary<string, Func<IEnumerable<Report>,IEnumerable<Report>>>
{
    { "FirstColumn", (IEnumerable<Report> reports) => 
                          reports.OrderBy(report => report.FirstColumn) },
    { "SecondColumn", (IEnumerable<Report> reports) => 
                          reports.OrderBy(report => report.SecondColumn) },

    (etc)
};

Then use:

// For production usage, include some error checking!
return Orderings[sortColumn].Skip(skip).Take(pageSize).ToList();

If you can get SortColumn to be passed in as an appropriate Func (possibly by making your method generic) that would avoid the mess here.

Share:
13,714
Slee
Author by

Slee

Updated on June 14, 2022

Comments

  • Slee
    Slee almost 2 years

    How do I order by a passed string value on my list of objects? i need to do paging and sorting on my List(Of) objects the paging is no problem but I don;t know who to get the Order By to work.

    Here is what I am currently doing and it's working great:

    Return returnReports.Skip(PageSize * (PageNumber-1)).Take(PageSize).ToList()
    

    How do I get this to work?

    Return returnReports.OrderBy(SortColumn).Skip(skip).Take(PageSize).ToList()
    

    SortColumn being a passed string value

  • mmx
    mmx about 15 years
    Action<T> delegate does not return a value.
  • mmx
    mmx about 15 years
    @Richard: It's funny cause I've been struggling to make sure the VB one is correct (as a C# guy) that I missed this while translating to C#.
  • Slee
    Slee about 15 years
    this is great - I'll have to study this. One more thing, how would I make this so I could say if i wanted it ascending or descending in order?
  • mmx
    mmx about 15 years
    Just copy and paste it and do a replace all OrderBy -> OrderByDescending. By the way, this is not a very performant method as it has to use reflection and build the expression dynamically. If you know the type you're working with, go with Jon's solution.