3

I didn't think this would be so rare to find but as it seems, it is. Here's the situation.

I've a ApplySort method which takes in 3 parameters posted from a mvc page.

    private List<dynamic> ApplySort(List<dynamic> listToBeSorted, string sortBy, string sortOrder)
    {
        if (String.IsNullOrEmpty(sortBy))
            sortBy = "createddate";
        if (String.IsNullOrEmpty(sortOrder) || sortOrder.Trim() != "0")
            sortOrder = "1"; // 1 = descending, 0 = ascending
        if (sortOrder == "1")
        {
            switch (sortBy)
            {
                case "name":
                    listToBeSorted = listToBeSorted.OrderByDescending(a => a.name).ToList();
                    break;
                case "assigned":
                    listToBeSorted = listToBeSorted.OrderByDescending(a => a.title).ToList();
                    break;
                case "duedate":
                    listToBeSorted = listToBeSorted.OrderByDescending(a => a.end).ToList();
                    break;
                case "status":
                    listToBeSorted = listToBeSorted.OrderByDescending(a => a.title).ToList();
                    break;
                default:
                    listToBeSorted = listToBeSorted.OrderByDescending(a => a.title).ToList();
                    break;
            }
        }
        else
        {
            // same code as in if-block, with just OrderBy calls instead of OrderByDescending
        }
        return listToBeSorted;
    }

Two problems:

1) Method seems unnecessarily long (with very similar code inside if and else blocks).

2) I want to be able to sort using multiple columns. sortBy param can have values like "name,title,createddate,status". So the sort applied should be, first by name, thenby title, then by createddate...and so on. I can use ThenBy by checking params sequentially. But how to dynamically apply a chain of ThenBy(s) based on the param value, where the number of ThenBy can vary.

string[] sortParams = sortBy.Split(new string[] { "," }, StringSplitOptions.RemoveEmptyEntries);
listToBeSorted.OrderBy(i=>i.sortParams[0]).ThenBy(j=>j.sortParams[1]).ThenBy(k=>k.sortParams[2])...(so on till sortParams.length)

How would I do this ? Plus, also how can I use the sortOrder parameter to sort by ascending or descending in the same line instead of using if-else.

MrClan
  • 5,084
  • 7
  • 26
  • 41
  • 1
    Google for "Dlinq", it'll allow you to do `.ThenBy(new string[])`. – Erti-Chris Eelmaa Jan 16 '15 at 10:36
  • Not quite a duplicate, but [this answer](http://stackoverflow.com/a/12137169/215380) might give you some inspiration. It uses an enum rather than strings to define the search properties, but that shouldn't be a problem. – Rawling Jan 16 '15 at 10:39
  • 1
    For your scenario, seems like having a dictionary of property names and respective lambdas should help you eliminate all redundancies. – Vikas Gupta Jan 16 '15 at 10:41
  • possible duplicate of [Dynamic LINQ OrderBy on IEnumerable](http://stackoverflow.com/questions/41244/dynamic-linq-orderby-on-ienumerablet) – gsharp Jan 16 '15 at 12:08

2 Answers2

0

You can create dictionary with Func<string, IComparable> mapping to properties of your class.

public class ItemWithProperty
{
    public string Property { get; set; }
}

public static void Main()
{
    Dictionary<string, Func<ItemWithProperty, IComparable>> stringPropertyMap = new Dictionary<string, Func<ItemWithProperty, IComparable>>()
    {
       {"param1", item => item.Property}
    };

    List<ItemWithProperty> toBeOrdered = new List<ItemWithProperty>();

    string[] parameters = {"param1"};
    var sorted = toBeOrdered.OrderBy(stringPropertyMap[parameters[0]]);
}
smiech
  • 640
  • 4
  • 8
  • 1
    But even with this, I'll have to call stringPropertyMap.Add() for every item in sortParams, isn't it ??? – MrClan Jan 16 '15 at 10:51
  • You call it only once to map IComparable properties of your class to string values.Then you can chain orderby, thenby using your parameters like you stated. Actually you should use IComparable instead of object - edited my answer. – smiech Jan 16 '15 at 11:01
  • @MrClan actually Add() in my code was not necessary, since I initialized the dict with values – smiech Jan 16 '15 at 12:07
-1

You use this if you change your method signature:

    private static IEnumerable<dynamic> ApplySort(IEnumerable<dynamic> listToBeSorted, ICollection<KeyValuePair<string, string>> sorters)
    {
        var orderBy = (sorters == null || sorters.Count == 0) ? new KeyValuePair<string, string>("createddate", "1") : sorters.First();
        var thenBys = (sorters == null || sorters.Count == 1) ? new List<KeyValuePair<string, string>>() : sorters.Except(Enumerable.Repeat(orderBy, 1));

        var orderedEnumerable = orderBy.Value == "1"
            ? listToBeSorted.OrderBy(x => GetPropertyValue(x, orderBy.Key))
            : listToBeSorted.OrderByDescending(x => GetPropertyValue(x, orderBy.Key));

        orderedEnumerable = thenBys.Aggregate(orderedEnumerable, (current, thenBy) => thenBy.Value == "1"
            ? current.ThenBy(x => GetPropertyValue(x, thenBy.Key))
            : current.ThenByDescending(x => GetPropertyValue(x, thenBy.Key)));

        return orderedEnumerable.ToList();
    }

    private static object GetPropertyValue(dynamic obj, string propName)
    {
        Type t = obj.GetType();
        return t.GetProperty(propName).GetValue(obj, null);
    }

Try :

    static void Main(string[] args)
    {
        var list = new List<dynamic>();
        list.Add(new { name = "Billy" });
        list.Add(new { name = "Johnny" });
        list.Add(new { name = "Ali" });

        var list2 = ApplySort(list, new List<KeyValuePair<string, string>>(new List<KeyValuePair<string, string>> { new KeyValuePair<string, string>("name", "1") }));

        foreach (var o in list2)
        {
            Console.WriteLine(o.name);
        }

        Console.ReadLine();

    }
Onur Gazioğlu
  • 461
  • 2
  • 12