4/7/12

extending System.Linq.IOrderedEnumerable

Recently I was writing code to develop a web page that displayed a list of users. The page also had a drop down list containing the following sort
options:

Sort by last Name Descending
Sort by last Name ascending
Sort by Age descending
Sort by Age ascending
Sort by Activity Date desc
Sort by Activity Date asc
Sort by Login Date desc

The users have to be grouped by Location by default and then the selected sort was to be applied within each group.
The tricky business requirement was that if there is a tie while sorting by age or Last Name, the tied users had to be further sorted by Activity Date
descending and if there was still a tie, the users had to be sorted by Login Date descending.

Assume that the user decided to sort the default list using one of the options above. Let us call this variable "UserSort".
if the UserSort was "LastNameDesc" then we need to sort by the DB field called LastName.
if the UserSort was "LastNameDesc" then we need to sort by the DB field called LastName in descending order
if the UserSort was "Age" then we need to sort by the DB field called Age

So we need to determine the DB field to sort by, based on the user selection. We cannot do that using the existing OrderBy methods on IEnumerable
as shown below,since we do not know what field we will be sorting by.



IEnumerable lstUsers = GetUsers();
lstUsers = lstUsers.OrderBy(x=>x.Location) //default ordering
           .ThenOrderBy(WHAT FIELD DO WE SPECIFY HERE?)


This is where Extension methods come into play. following is the extension method on IOrderedEnumerable that enables us to add the required order
to the list based on the user's selected sort option.


public static IOrderedEnumerable ThenByCustomSort(this IOrderedEnumerable defaultList,string sortOrder)
{
switch(sortOrder)
{
case "LastNameDesc":
return defaultList.ThenByDescending(x=>x.LastName);
case "LastNameAsc":
return defaultList.ThenBy(x=>x.LastName);
case "AgeDesc":
return defaultList.ThenByDescending(x=>x.Age);
case "AgeAsc":
return defaultList.ThenBy(x=>x.Age);
case "ActivityDateDesc":
return defaultList.ThenByDescending(x=>x.ActivityDate);
case "ActivityDateAsc":
return defaultList.ThenBy(x=>x.ActivityDate);
case "LoginDateDesc":
return defaultList.ThenByDescending(x=>x.LoginDate);
default:
return defaultList;
}

}


This is how we add call this method:


IEnumerable lstUsers = GetUsers();
lstUsers = lstUsers.OrderBy(x=>x.Location)
           .ThenByCustomSort(UserSort);


Now coming to the tie in the results.We do something similar and create an extension method as shown below:


public static IOrderedEnumerable ThenByTieBreakSort(this IOrderedEnumerable defaultList, string sortOrder)
{ 
switch(sortOrder)
{
  case "LastNameDesc": 
  case "LastNameAsc":
  case "AgeDesc":
  case "AgeAsc":
   return defaultList.ThenByCustomSort("ActivityDateDesc").ThenByCustomSort("LoginDateDesc");
 case "ActivityDateDesc": 
  return defaultList.ThenByCustomSort("LoginDateDesc");
 default:
  return defaultList;
}
}


After adding the tiebreak the code looks now looks as shown below:


IEnumerable lstUsers = GetUsers();
lstUsers = lstUsers.OrderBy(x=>x.Location)
           .ThenByCustomSort(UserSort)
           .ThenByTieBreakSort(UserSort);

IMPORTANT:
Since LINQ queries are executed by means of deferred execution, we need to ensure that we are adding additional sort options well before the
query gets executed, which is exactly what the use of the extension methods above does.

NOTE:
I have used string data type for the sortOrder just for the purposes of blogging. It is better to use some strongly typed data type, like enums,
for the purpose. I have also not taken string casing into consideration.

Peace out.

No comments: