22

I'm creating a mock data source that I want to be able to pass in a list of SortExpressions on.

public SortExpression(string name, SortDirection direction)
{
     this.name = name;
     this.direction = direction;
}

Update with Jon Skeet's code and also the entire class. GetData() is just populating the object with x number of records.

public class Data
{

public int Id { get; set; }
public Guid gId { get; set; }
public string Name { get; set; }
public string Phone { get; set; }
public string Address { get; set; }
public DateTime Created { get; set; }
public string SortMe { get; set; }
public static List<Data> GetFakeData(int start, int numberToFetch, IList<SortExpression> sortExpressions, IList<FilterExpression> filterExpressions, out int totalRecords)
{
    DataCollection items = GetData();
    IEnumerable<Data> query = from item in items select item;

    bool sortExpressionsExist = sortExpressions != null;
    if (sortExpressionsExist)
    {
        // Won't be read in the first iteration; will be written to
        IOrderedEnumerable<Data> orderedQuery = null;
        for (int i = 0; i < sortExpressions.Count; i++)
        {
            // Avoid single variable being captured: capture one per iteration.
            // Evil bug which would be really hard to find :)
            int copyOfI = i;
            // Tailor "object" depending on what GetProperty returns.
            Func<Data, object> expression = item =>
                  item.GetType().GetProperty(sortExpressions[copyOfI].Name);

            if (sortExpressions[i].Direction == SortDirection.Ascending)
            {
                orderedQuery = (i == 0) ? query.OrderBy(expression)
                                        : orderedQuery.ThenBy(expression);
            }
            else
            {
                orderedQuery = (i == 0) ? query.OrderByDescending(expression)
                                        : orderedQuery.ThenByDescending(expression);
            }
        }
        query = orderedQuery;
    }

    bool filterExpressionsExist = filterExpressions != null;
    if (filterExpressionsExist)
    {
        foreach (var filterExpression in filterExpressions)
        {
            query.Where(item => item.GetType().GetProperty(filterExpression.ColumnName).GetValue(item, null).ToString().Contains(filterExpression.Text));
        }
    }
    totalRecords = query.Count();


       return query.Skip(start).Take(numberToFetch).ToList<Data>();
    }
}

Doesn't seem to be doing anything. Compiles, no errors, just no sort. Any ideas?

rball
  • 6,925
  • 7
  • 49
  • 77

6 Answers6

27

There are two problems. The first is the one others have alluded to - you need to use the value returned by OrderBy etc. The second is that each time you call OrderBy, that's adding a new "primary" ordering. You really want ThenBy after the first ordering has been applied. That makes it pretty ugly, unfortunately. It's still pretty ugly after a refactoring, but not too bad...

IEnumerable<Data> query = from item in items select item;
if (sortExpressionsExist)
{
    // Won't be read in the first iteration; will be written to
    IOrderedEnumerable<Data> orderedQuery = null;
    for (int i = 0; i < sortExpressions.Count; i++)
    {
        // Avoid single variable being captured: capture one per iteration.
        // Evil bug which would be really hard to find :)
        int copyOfI = i;
        // Tailor "object" depending on what GetProperty returns.
        Func<Data, object> expression = item => 
              item.GetType()
                  .GetProperty(sortExpressions[copyOfI].Name)
                  .GetValue(item, null);

        if (sortExpressions[i].Direction == SortDirection.Ascending)
        {
            orderedQuery = (i == 0) ? query.OrderBy(expression)
                                    : orderedQuery.ThenBy(expression);
        }
        else
        {
            orderedQuery = (i == 0) ? query.OrderByDescending(expression)
                                    : orderedQuery.ThenByDescending(expression);
        }
    }
    query = orderedQuery;
}
Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
5

OrderBy returns a new IEnumerable, so you need to do something like:

IEnumerable<Data> results 
    = query.OrderBy(item => item.GetType().GetProperty(sortExpressions[i].Name));
Reed Copsey
  • 554,122
  • 78
  • 1,158
  • 1,373
2

This will work:

YourCollection.Orderby(item => item.Property1).ThenBy(item => item.Property2);
Nalan Madheswaran
  • 10,136
  • 1
  • 57
  • 42
2

The OrderBy/OrderByDescending 'operators' work like String.ToUpper(), i.e., they take the thing you invoke it on, and yield a 'copy' that has what you asked for.

In other words, instead of saying:

query.Orderby(item->item.X)

you should do

query = query.Orderby(item->item.X)

or

sortedResult = query.Orderby(item->item.X)

[And as Jon Skeet points out, use ThenBy/ThenByDescending as in his answer]

Ruben Bartelink
  • 59,778
  • 26
  • 187
  • 249
2

The query is not mutable, so OrderBy returns a new object. You need to make the same call, but add "query =" to the beginning.

query = query.OrderBy(item => item.GetType().GetProperty(sortExpressions[i].Name));
VoodooChild
  • 9,776
  • 8
  • 66
  • 99
stevedbrown
  • 8,862
  • 8
  • 43
  • 58
2

OrderBy on IEnumerable returns returns an IOrderedEnumerable. It does not sort them in line. So get the return value from your .OrderBy and you will be fine.

JP Alioto
  • 44,864
  • 6
  • 88
  • 112