tags:

views:

2176

answers:

5

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?

+4  A: 

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
+2  A: 

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
+2  A: 

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

stevedbrown
+2  A: 

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
+7  A: 

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
Yeah, that's why I'm doing the for loop instead of the foreach, because I was thinking that I needed a ThenBy in there someplace.
rball
I've just fixed a bug, by the way - you need the copyOfI part as otherwise the wrong variable will be captured!
Jon Skeet
Crap, still isn't working.
rball
Yeah, got that part. I'll refresh to see if you did any further updates.
rball
So what's it doing? Sorting at all? Sorting by just the first field? Just the last?
Jon Skeet
No sorting at all. Same as before.
rball
What's the type of GetProperty? I'm not quite sure what OrderBy will do if it really *is* object...
Jon Skeet
I updated query with more code.
rball
What's the type of GetProperty? PropertyInfo, I believe
rball
I think I got it (well you did all the work): Func<Data, object> expression = item => item.GetType().GetProperty(sortExpressions[copyOfI].Name).GetValue(item, null);
rball
Thanks for the answer! Very much appreciated.
rball
Ah, I didn't realise it was just a plain property. Have updated code appropriately.
Jon Skeet