views:

3337

answers:

4

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

+1  A: 

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);
David Basarab
Action<T> delegate does not return a value.
Mehrdad Afshari
You are right I will correct.
David Basarab
+6  A: 

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);
  }
}
Mehrdad Afshari
That's not going to work when SortColumn is passed in as a string though.
Jon Skeet
Oh, I didn't notice that in the question, editing...
Mehrdad Afshari
You've missed the this from the e parameter in C# code, as they stand they are not extension methods.
Richard
@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#.
Mehrdad Afshari
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?
Slee
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.
Mehrdad Afshari
got it - thanks - and it's running plenty fast.
Slee
+1  A: 

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.

Jon Skeet
+2  A: 

Hi,

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.

Tomas Petricek