7

I have a collection of CLR objects. The class definition for the object has three properties: FirstName, LastName, BirthDate.

I have a string that reflects the name of the property the collection should be sorted by. In addition, I have a sorting direction. How do I dynamically apply this sorting information to my collection? Please note that sorting could be multi-layer, so for instance I could sort by LastName, and then by FirstName.

Currently, I'm trying the following without any luck:

var results = myCollection.OrderBy(sortProperty);

However, I'm getting a message that says:

... does not contain a defintion for 'OrderBy' and the best extension method overload ... has some invalid arguments.

9 Answers 9

10

Okay, my argument with SLaks in his comments has compelled me to come up with an answer :)

I'm assuming that you only need to support LINQ to Objects. Here's some code which needs significant amounts of validation adding, but does work:

// We want the overload which doesn't take an EqualityComparer.
private static MethodInfo OrderByMethod = typeof(Enumerable)
    .GetMethods(BindingFlags.Public | BindingFlags.Static)
    .Where(method => method.Name == "OrderBy" 
           && method.GetParameters().Length == 2)
    .Single();

public static IOrderedEnumerable<TSource> OrderByProperty<TSource>(
    this IEnumerable<TSource> source,
    string propertyName) 
{
    // TODO: Lots of validation :)
    PropertyInfo property = typeof(TSource).GetProperty(propertyName);
    MethodInfo getter = property.GetGetMethod();
    Type propType = property.PropertyType;
    Type funcType = typeof(Func<,>).MakeGenericType(typeof(TSource), propType);
    Delegate func = Delegate.CreateDelegate(funcType, getter);
    MethodInfo constructedMethod = OrderByMethod.MakeGenericMethod(
        typeof(TSource), propType);
    return (IOrderedEnumerable<TSource>) constructedMethod.Invoke(null,
        new object[] { source, func });
}

Test code:

string[] foo = new string[] { "Jon", "Holly", "Tom", "William", "Robin" };

foreach (string x in foo.OrderByProperty("Length"))
{
    Console.WriteLine(x);
}

Output:

Jon
Tom
Holly
Robin
William

It even returns an IOrderedEnumerable<TSource> so you can chain ThenBy clauses on as normal :)

Sign up to request clarification or add additional context in comments.

9 Comments

How about customers.OrderByProperty("LastName").ThenByProperty("FirstName") or customers.OrderByProperty("LastName", "FirstName") (where these are params string[]), as it seems to be part of his requirement. Would that be workable in this solution?
@Anthony: You'd have to write more code to do that of course, but it would be possible - and look a lot like the code above.
OK. And as I'm someone who knows jack squat about the topic, is there a reason you would prefer reflection over @SLaks' use of expression trees? (In case I'm presented with such a requirement in the future.)
@Anthony: Well, my version works with LINQ to Objects directly - SLaks' version needs an IQueryable<T>. You can create one using the AsQueryable operator, but I would personally stick with this version.
@Jon, @Anthony: My version doesn't need an IQueryable<T>; you can just compile the expression. However, it'll be slower than yours since it needs to compile an expression. (which could be cached)
|
7

You need to build an Expression Tree and pass it to OrderBy.
It would look something like this:

var param = Expression.Parameter(typeof(MyClass));
var expression = Expression.Lambda<Func<MyClass, PropertyType>>(
    Expression.Property(param, sortProperty),
    param
);

Alternatively, you can use Dynamic LINQ, which will allow your code to work as-is.

6 Comments

How do I build an Expression Tree?
Given that the OP has "a collection of CLR objects" I suspect he's not after an IQueryable solution.
@Jon: So what? You still need an expression tree to sort dynamically. That just means he'll need to call Compile.
@SLaks: I disagree. I think it's entirely possible to sort dynamically without an expression tree.
How applicable would this solution (or Jon's hypothetical solution) be if the type of sortProperty was not fixed?
|
1
protected void sort_grd(object sender, GridViewSortEventArgs e)
    {
        if (Convert.ToBoolean(ViewState["order"]) == true)
        {
            ViewState["order"] = false;

        }
        else
        {
            ViewState["order"] = true;
        }
        ViewState["SortExp"] = e.SortExpression;
        dataBind(Convert.ToBoolean(ViewState["order"]), e.SortExpression);
    }

public void dataBind(bool ord, string SortExp)
    {
        var db = new DataClasses1DataContext(); //linq to sql class
        var Name = from Ban in db.tbl_Names.AsEnumerable()
                         select new
                         {
                             First_Name = Ban.Banner_Name,
                             Last_Name = Ban.Banner_Project
                         };
        if (ord)
        {
            Name = BannerName.OrderBy(q => q.GetType().GetProperty(SortExp).GetValue(q, null));
        }
        else
        {
            Name = BannerName.OrderByDescending(q => q.GetType().GetProperty(SortExp).GetValue(q, null));
        }
        grdSelectColumn.DataSource = Name ;
        grdSelectColumn.DataBind();
    }

Comments

0

you can do this with Linq

var results = from c in myCollection
    orderby c.SortProperty
    select c;

2 Comments

The problem is, my sortProperty is a string. How can I do a sort with a string like that?
ah, i see. Found another post here that may help... stackoverflow.com/questions/4275878/…
0

For dynamic sorting you could evaluate the string i.e. something like

List<MyObject> foo = new List<MyObject>();
string sortProperty = "LastName";
var result = foo.OrderBy(x =>
                {
                if (sortProperty == "LastName")
                    return x.LastName;
                else
                    return x.FirstName;
                });

For a more generic solution see this SO thread: Strongly typed dynamic Linq sorting

1 Comment

Agreed, it's not a very generic solution, but solves this particular problem
0

For this sort of dynamic work I've been using the Dynamic LINQ library which makes this sort of thing easy:

http://weblogs.asp.net/scottgu/archive/2008/01/07/dynamic-linq-part-1-using-the-linq-dynamic-query-library.aspx

http://msdn2.microsoft.com/en-us/vcsharp/bb894665.aspx

1 Comment

I misunderstood, I've updated my answer to suggest using the Dynamic Linq library.
0

You can copy paste the method I post in that answer, and change the signature/method names: How to make the position of a LINQ Query SELECT variable

Comments

0

You can actually use your original line of code

var results = myCollection.OrderBy(sortProperty);

simply by using the System.Linq.Dynamic library.

If you get a compiler error (something like cannot convert from or does not contain a definition...) you may have to do it like this:

var results = myCollection.AsQueryable().OrderBy(sortProperty);

No need for any expression trees or data binding.

Comments

0

You will need to use reflection to get the PropertyInfo, and then use that to build an expression tree. Something like this:

var entityType = typeof(TEntity);
var prop = entityType.GetProperty(sortProperty);
var param = Expression.Parameter(entityType, "x");
var access = Expression.Lambda(Expression.MakeMemberAccess(param, prop), param);

var ordered = (IOrderedQueryable<TEntity>) Queryable.OrderBy(
    myCollection, 
    (dynamic) access);

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.