views:

26100

answers:

15

I found an example in the VS2008 Examples for Dynamic LINQ that allows you to use a sql-like string (e.g. OrderBy("Name, Age DESC")) for ordering. Unfortunately, the method included only works on IQueryable<T>. Is there any way to get this functionality on IEnumerable<T>?

A: 

Is this what you're thinking of?


public static void OrderByExample()
{
    Pet[] pets = { new Pet { Name="Barley", Age=8 },
                   new Pet { Name="Boots", Age=4 },
                   new Pet { Name="Whiskers", Age=1 } };

    IEnumerable query = pets.OrderBy(pet => pet.Age);

    foreach (Pet pet in query)
    {
        Console.WriteLine("{0} - {1}", pet.Name, pet.Age);
    }
}

Source: http://msdn.microsoft.com/en-us/library/bb534966.aspx

D2VIANT
+4  A: 

I guess it would work to use reflection to get whatever property you want to sort on:

IEnumerable<T> myEnumerables
var query=from enumerable in myenumerables
          where some criteria
          orderby GetPropertyValue(enumerable,"SomeProperty")
          select enumerable

private static object GetPropertyValue(object obj, string property)
{
    System.Reflection.PropertyInfo propertyInfo=obj.GetType().GetProperty(property);
    return propertyInfo.GetValue(obj, null);
}

Note that using reflection is considerably slower than accessing the property directly, so the performance would have to be investigated.

Kjetil Watnedal
does this even work? orderby does not want a value but a selector lamba/delegate (Func<TSource, TKey> keySelector)..
Davy Landman
I did try this example before posting it, and yes, it does work.
Kjetil Watnedal
This solution works well for simple single property sorting.
Jeff Schumacher
+1 This is exactly what I was looking for! This will work great for simple page sorting issues.
Andrew Siemer
This didn't work for me. Am I missing something? What should "SomeProperty" be. I tried giving the property name as well as property.GetType(). I have IQueryable<> and not IEnumerable<>
Rashmi Pandit
+1  A: 

You could add it:

public static IEnumerable<T> OrderBy( this IEnumerable<T> input, string queryString) {
    //parse the string into property names
    //Use reflection to get and sort by properties
    //something like

    foreach( string propname in queryString.Split(','))
        input.OrderBy( x => GetPropertyValue( x, propname ) );

    // I used Kjetil Watnedal's reflection example
}

The GetPropertyValue function is from Kjetil Watnedal's answer

The issue would be why? Any such sort would throw exceptions at run-time, rather than compile time (like D2VIANT's answer).

If you're dealing with Linq to Sql and the orderby is an expression tree it will be converted into SQL for execution anyway.

Keith
I'm really wondering where you got the GetPropertyValue function? Searching for "kjetil.watnedal" did not result in anything interesting..
Davy Landman
See @Kjetil Watnedal's previous answer.
Keith
damn... looked totally over than one...
Davy Landman
+19  A: 

I found the answer. I can use the .AsQueryable<>() extension method to convert my list to IQueryable, then run the dynamic order by against it.

John Sheehan
+83  A: 

Just stumbled into this oldie...

To do this without the dynamic LINQ library, you just need the code as below. This covers most common scenarios including nested properties.

To get it working with IEnumerable<T> you could add some wrapper methods that go via AsQueryable - but the code below is the core Expression logic needed.

    public static IOrderedQueryable<T> OrderBy<T>(this IQueryable<T> source, string property)
    {
        return ApplyOrder<T>(source, property, "OrderBy");
    }
    public static IOrderedQueryable<T> OrderByDescending<T>(this IQueryable<T> source, string property)
    {
        return ApplyOrder<T>(source, property, "OrderByDescending");
    }
    public static IOrderedQueryable<T> ThenBy<T>(this IOrderedQueryable<T> source, string property)
    {
        return ApplyOrder<T>(source, property, "ThenBy");
    }
    public static IOrderedQueryable<T> ThenByDescending<T>(this IOrderedQueryable<T> source, string property)
    {
        return ApplyOrder<T>(source, property, "ThenByDescending");
    }
    static IOrderedQueryable<T> ApplyOrder<T>(IQueryable<T> source, string property, string methodName) {
        string[] props = property.Split('.');
        Type type = typeof(T);
        ParameterExpression arg = Expression.Parameter(type, "x");
        Expression expr = arg;
        foreach(string prop in props) {
            // use reflection (not ComponentModel) to mirror LINQ
            PropertyInfo pi = type.GetProperty(prop);
            expr = Expression.Property(expr, pi);
            type = pi.PropertyType;
        }
        Type delegateType = typeof(Func<,>).MakeGenericType(typeof(T), type);
        LambdaExpression lambda = Expression.Lambda(delegateType, expr, arg);

        object result = typeof(Queryable).GetMethods().Single(
                method => method.Name == methodName
                        && method.IsGenericMethodDefinition
                        && method.GetGenericArguments().Length == 2
                        && method.GetParameters().Length == 2)
                .MakeGenericMethod(typeof(T), type)
                .Invoke(null, new object[] {source, lambda});
        return (IOrderedQueryable<T>)result;
   }
Marc Gravell
Best damn piece of code I have seen :) Just solved a million problems in my project :)
Syed Sajid Nizami
Nice! Helped me out too.
Jonathan Moffatt
Marc, Do you have the similar extension for LIKE Condition?
Prasad
@Prasad - `LIKE` is a bit different, but which back end? For LINQ-to-SQL, there is `SqlMethods.Like`: http://msdn.microsoft.com/en-us/library/system.data.linq.sqlclient.sqlmethods.like.aspx; can you add more info what you are looking for?
Marc Gravell
I am using SQL as backend, i need to do a search operation with dynamic parameter names as like in your OrderBy extension.
Prasad
Well, you can certainly use `SqlMethods.Like` in a custom expression; it really isn't clear where it breaks down... can you clarify? Perhaps ask as a question?
Marc Gravell
Thanks Mark, I did in http://stackoverflow.com/questions/1654745/dynamic-linq-like yesterday. Its better if i have a sample on it
Prasad
I've answered it there, then.
Marc Gravell
Sounds like lots of folks had success with this code- I can't for the life of me figure out how to apply it! These are extension methods, right? How do I use them??
Dave Swersky
@Dave - you need to start with `IQueryable<T>`, so if you have something like `List<T>` (which is `IEnumerable<T>`) you may need to use `AsQueryable()` - for example `var sorted = someList.AsQueryable().OrderBy("Foo.Bar");`
Marc Gravell
@Marc Gravell - Thanks Marc!!
Dave Swersky
+1 Thank you very much for this solution - although I had to slightly customize it to work with RIA services, tha basic idea worked well!
gius
Have you seen this... it might help some people... http://stackoverflow.com/questions/557819/strongly-typed-dynamic-linq-sorting/2794039#2794039 its a more strongly typed solution.
anthonyv
Great code ! Do you know any way to add support to the "Count()" extension method ? Calling an extension method is quiet a pain, and I have troubles building an expression with those.I'm trying to add the 'Count()' support for IQueryable<T> which is coded as extension in the 'Queryable' type.
Mose
Is there any example of query using this code?
ile
@ile - `var ordered = someData.OrderBy("Name");` - or for `IEnumerable<T>` data, `var ordered = someData.AsQueryable().OrderBy("Name");`
Marc Gravell
Ty Marc. You saved my day.
Arnis L.
This be done already with *AsQueryAble().OrderBy().ThenBy()*. There must be something I'm missing. **Edit** I just noticed the above methods are returning *IOrderedQueryable* and the methods I listed are returning *IOrderedEnumerable*.
Chuck Conway
Ok, I get it. I'm encountering the same problem as the question.
Chuck Conway
A: 

I've stumble this question looking for Linq multiple orderby clauses and maybe this was what the author was looking for

Here's how to do that:

IEnumerable query = pets.OrderBy(pet => pet.Name).ThenByDescending(pet => pet.Age);
DK39
+1 canceled the down-vote due to lack of explanation. I also think the author might have been interested in multiple order-bys. Even if dynamic *was* the key word, no reason to down-vote.
uosɐſ
A: 

An alternate solution uses the following class/interface. It's not truly dynamic, but it works.

public interface IID
{
    int ID
    {
        get; set;
    }
}

public static class Utils
{
    public static int GetID<T>(ObjectQuery<T> items) where T:EntityObject, IID
    {
        if (items.Count() == 0) return 1;
        return items.OrderByDescending(u => u.ID).FirstOrDefault().ID + 1;
    }
}
Mike Christiansen
+2  A: 

Just building on what others have said. I found that the following works quite well.

   public static IEnumerable<T> OrderBy<T>(this IEnumerable<T> input, string queryString)
    {
        if (string.IsNullOrEmpty(queryString))
            return input;

        int i = 0;
        foreach (string propname in queryString.Split(','))
        {
            var subContent = propname.Split('|');
            if (Convert.ToInt32(subContent[1].Trim()) == 0)
            {
                if (i == 0)
                    input = input.OrderBy(x => GetPropertyValue(x, subContent[0].Trim()));
                else
                    input = ((IOrderedEnumerable<T>)input).ThenBy(x => GetPropertyValue(x, subContent[0].Trim()));
            }
            else
            {
                if (i == 0)
                    input = input.OrderByDescending(x => GetPropertyValue(x, subContent[0].Trim()));
                else
                    input = ((IOrderedEnumerable<T>)input).ThenByDescending(x => GetPropertyValue(x, subContent[0].Trim()));
            }
            i++;
        }

        return input; 
    }
vdhant
I'm not a big fan of the way this was implemented, and would prefer that the string manipulation happen somewhere else and that the sorting method take a more explicit data structure as its input, but I gave it a +1 because it helped me finish my own implementation. I was missing the case to IOrderedEnumerable and was having trouble with the .ThenBy() calls.
Seth Petry-Johnson
A: 

I was trying to do this but having problems with Kjetil Watnedal's solution because I don't use the inline linq syntax - I prefer method-style syntax. My specific problem was in trying to do dynamic sorting using a custom IComparer.

My solution ended up like this:

Given an IQueryable query like so:

    List<DATA__Security__Team> teams = TeamManager.GetTeams();
    var query = teams.Where(team => team.ID < 10).AsQueryable();

And given a run-time sort field argument:

    string SortField; // Set at run-time to "Name"

The dynamic OrderBy looks like so:

    query = query.OrderBy(item => item.GetReflectedPropertyValue(SortField));

And that's using a little helper method called GetReflectedPropertyValue():

    public static string GetReflectedPropertyValue(this object subject, string field)
    {
        object reflectedValue = subject.GetType().GetProperty(field).GetValue(subject, null);
        return reflectedValue != null ? reflectedValue.ToString() : "";
    }


One last thing - I mentioned that I wanted the OrderBy to use custom IComparer - because I wanted to do Natural sorting.

To do that, I just alter the OrderBy to:

    query = query.OrderBy(item => item.GetReflectedPropertyValue(SortField), new NaturalSortComparer<string>());

See this post for the code for NaturalSortComparer().

James McCormack
A: 

Here's something else I found interesting. If your source is a DataTable, you can use dynamic sorting without using Dynamic Linq

DataTable orders = dataSet.Tables["SalesOrderHeader"];
EnumerableRowCollection<DataRow> query = from order in orders.AsEnumerable()
                                         orderby order.Field<DateTime>("OrderDate")
                                         select order;
DataView view = query.AsDataView();
bindingSource1.DataSource = view;

reference: http://msdn.microsoft.com/en-us/library/bb669083.aspx (Using DataSetExtensions)

Here is one more way to do it by converting it to a DataView:

DataTable contacts = dataSet.Tables["Contact"];    
DataView view = contacts.AsDataView();    
view.Sort = "LastName desc, FirstName asc";    
bindingSource1.DataSource = view;
dataGridView1.AutoResizeColumns();
Sameer Alibhai
A: 

I have an IQueryable union that I could not sort via .OrderBy(stringExpression) because it was throwing a "The type arguments for method ... cannot be inferred from the usage." syntax error at compile time. So, I created a static IOrderedQueryableClass from the code you provided above and got IOrderedQueryable.OrderBy(unionQuery, propertyName) working. Thank you.

Unfortunately, all other instances of .OrderBy(stringExpression) threw exceptions at runtime when the stringExpression did not resolve to a single property name. So, I encapsulated my IOrderedQueryableClass in a new namespace. As a result, all the old .OrderBy(stringExpression) worked once again. But, I couldn't compile my IOrderedQueryable.OrderBy(unionQuery, propertyName) because the OrderBy method was ambiguous between System.Linq.IOrderedQueryable and my new namespace. To make my union orderby work again, I had to fully qualify references to MyNewNamespace.IOrderedQueryableClass.IOrderedQueryable.OrderBy.

Carl Kelley
A: 

See if this helps in any way

http://www.onedotnetway.com/dynamic-sort-with-linq-to-sql/

John
+3  A: 

Just stumbled across this question.

Using Marc's ApplyOrder implementation from above, I slapped together an Extension method that handles SQL-like strings like:

list.OrderBy("MyProperty DESC, MyOtherProperty ASC");

Details can be found here: http://aonnull.blogspot.com/2010/08/dynamic-sql-like-linq-orderby-extension.html

Adam Anderson
A: 

Hey congratulations, the first code work for me , thk

Juan Camilo Villa
A: 
  foreach (ListItem i in cblPartyDetail.Items)
                {
                    if (i.Selected)
                    {
                        foreach (ListItem k in cblArticle.Items)
                        {
                            if (k.Selected)
                            {
                                foreach (ListItem l in cblLotNo.Items)
                                {
                                    if (l.Selected)
                                    {
                                        Fact = Fact.Where(x => (x.p.Party) == i.Value).Where(y => (y.i.Description) == k.Value).Where(x => (x.mt.dMyLot) == l.Value);
                                        FactoriesList = FactoriesList.Concat(Fact.Where(x => (x.p.Party) == i.Value).Where(y => (y.i.Description) == k.Value).Where(x => (x.mt.dMyLot) == l.Value).Select(b => b.Factory)).ToList();
                                    }
                                }
                            }
                        }
                    }
                }

I want to use && operator in where clause -Above condition get Unlimited time to run .I couldn't get proper result.

aakash
@AAkash: This isn't an answer to the question! Please delete this answer and start a new question.
Oliver