user564042
user564042

Reputation: 185

Dynamically Sorting with LINQ

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.

Upvotes: 7

Views: 4818

Answers (9)

StriplingWarrior
StriplingWarrior

Reputation: 156524

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);

Upvotes: 0

Kyle
Kyle

Reputation: 476

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.

Upvotes: 0

Kaviraj
Kaviraj

Reputation: 11

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();
    }

Upvotes: 1

Guillaume86
Guillaume86

Reputation: 14400

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

Upvotes: 0

Jon Skeet
Jon Skeet

Reputation: 1500515

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 :)

Upvotes: 10

Peter
Peter

Reputation: 9712

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

Upvotes: 0

BrokenGlass
BrokenGlass

Reputation: 160892

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

Upvotes: 0

SLaks
SLaks

Reputation: 887443

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.

Upvotes: 7

WraithNath
WraithNath

Reputation: 18013

you can do this with Linq

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

Upvotes: 0

Related Questions