Introduction
This article presents a small utility class to sort an "IEnumerable
" of objects on multiple fields by the field names provided at the run time using Linq. It also presents a WPF example on how to use this utility.
Background
The Language Integrated Query "Linq" provides many programming supports. This link is a very good place to learn how to use "Linq". Among these programming supports, sorting is one of the nice features from "Linq". The problem though is that we normally do not know which fields to sort at compilation time, so I feel the need for an utility to extend the sorting capability from "Linq" to address the two requirements:
- The utility should support multiple field sorting;
- The utility should be able to take the field names and the sorting orders of these fields at the run time without using complex "switch statements".
The utility is presented as an "extension method" in a static
class in this article. It allows us to sort an "IEnumerable" of objects on multiple fields by any field names at the run time. Although this article uses a WPF MVVM application to show you how to use the utility class, you do not need to have a lot of knowledge on MVVM and WPF to read this article. The utility class is pretty simple to use, so if you do not have sufficient knowledge in MVVM and WPF, you can skip the example.
The attached Visual Studio 2010 solution is a WPF "MVVM" project. The main components in this WPF project are the following:
- The "DynamicLinqMultiSortingUtility.cs" file in the "Utilities" folder implements the sorting utility class.
- The "StudentRepository.cs" file in the "Models" folder implements the application's data model.
- The "MainWindowViewModel.cs" file in the "ViewModels" folder implements the application's view model.
- The "MainWindow.xaml" file is the XAML view of the demo application.
I will first introduce the sorting utility class and then show you how to use it in the WPF application.
The Sorting Utility Class
The sorting utility class is implemented in the "DynamicLinqMultiSortingUtility.cs" file in the "Utilities" folder:
using System;
using System.Collections.Generic;
using System.Linq;
namespace DynamicLinqMultipleSort.Utilities
{
public static class LinqDynamicMultiSortingUtility
{
public static IEnumerable<T> MultipleSort<T>(this IEnumerable<T> data,
List<Tuple<string, string>> sortExpressions)
{
if ((sortExpressions == null) || (sortExpressions.Count <= 0))
{
return data;
}
IEnumerable<T> query = from item in data select item;
IOrderedEnumerable<T> orderedQuery = null;
for (int i = 0; i < sortExpressions.Count; i++)
{
var index = i;
Func<T, object> expression = item => item.GetType()
.GetProperty(sortExpressions[index].Item1)
.GetValue(item, null);
if (sortExpressions[index].Item2 == "asc")
{
orderedQuery = (index == 0) ? query.OrderBy(expression)
: orderedQuery.ThenBy(expression);
}
else
{
orderedQuery = (index == 0) ? query.OrderByDescending(expression)
: orderedQuery.ThenByDescending(expression);
}
}
query = orderedQuery;
return query;
}
}
}
The "extension method" "MultipleSort
" is the method that we can use to sort an "IEnumerable
" of objects.
- The "
sortExpressions
" parameter is a list of tuples, the first item of each tuple is the field name, the second item of each tuple is the sorting order (asc/desc). Both the field name and the sorting order are case sensitive. - If the field name (case sensitive) provided for sorting does not exist in the object, an exception is thrown.
- If a property name shows up more than once in the "
sortExpressions
" list, only the first takes effect. - If multiple field names are given for sorting, the first field will be the primary sorting field, the following fields will be used in the sorting in the "ThenBy" style.
If everything goes well, the sorted "IEnumerable
" of objects is returned from the method. In the following sections of this article, I will show you how to use this utility class in the WPF application.
The Data Model
In order to show you how to use the utility class, I need to generate an "IEnumerable
" of objects as the data model. The demo application's data model is implemented in the "StudentRepository.cs" file in the "Models" folder:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace DynamicLinqMultipleSort.Models
{
public class Student
{
public int Id { get; set; }
public string Name { get; set; }
public DateTime Enrollment { get; set; }
public int Score { get; set; }
}
public static class StudentRepository
{
private static List<Student> Students = null;
public static List<Student> GetStudentList()
{
if (Students != null)
{
return Students;
}
Students = new List<Student>();
var now = DateTime.Now;
var rand = new Random();
for (int i = 1; i <= 100; i++)
{
var student = new Student();
student.Id = i;
student.Name = "Student Name No." + (i % 10).ToString();
student.Enrollment = now.AddDays(i % 3);
student.Score = 60 + (int)(rand.NextDouble() * 40);
Students.Add(student);
}
return Students;
}
}
}
The "GetStudentList
" method in the "StudentRepository
" class randomly generates a "List" of "Student
" objects. The concrete "List
" class implements the "IEnumerable
" interface, so we can use this list of students in our example. I will be showing you how to sort this list of students using the utility class introduced in this article.
The View Model
The view model of the demo WPF application is implemented in the "MainWindowViewModel.cs" file in the "ViewModels" folder:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Documents;
using DynamicLinqMultipleSort.BindingUtilities;
using DynamicLinqMultipleSort.Models;
using DynamicLinqMultipleSort.Utilities;
namespace DynamicLinqMultipleSort.ViewModels
{
class MainWindowViewModel : ViewModelBase
{
private List<Student> students;
public List<Student> Students
{
get { return students; }
set
{
if (students != value)
{
students = value;
NotifyPropertyChanged("Students");
}
}
}
private string sortingString;
public string SortingString
{
get { return sortingString; }
set
{
if (sortingString != value)
{
sortingString = value;
NotifyPropertyChanged("SortingString");
}
}
}
private void WireCommands()
{
DoSortingCommand = new RelayCommand(DoSorting);
DoSortingCommand.IsEnabled = true;
}
public RelayCommand DoSortingCommand { get; private set; }
private void DoSorting()
{
var studentList = StudentRepository.GetStudentList();
string sortString = SortingString;
if (string.IsNullOrWhiteSpace(sortString))
{
ShowMessage("Please type in a sorting string.");
return;
}
try
{
var sortExpressions = new List<Tuple<string, string>>();
string[] terms = sortString.Split(',');
for (int i = 0; i < terms.Length; i++)
{
string[] items = terms[i].Trim().Split('~');
var fieldName = items[0].Trim();
var sortOrder = (items.Length > 1)
? items[1].Trim().ToLower() : "asc";
if ((sortOrder != "asc") && (sortOrder != "desc"))
{
throw new ArgumentException("Invalid sorting order");
}
sortExpressions.Add(new Tuple<string, string>(fieldName, sortOrder));
}
studentList = studentList.MultipleSort(sortExpressions).ToList();
Students = studentList;
}
catch (Exception e)
{
var msg = "There is an error in your sorting string.
Please correct it and try again - "
+ e.Message;
ShowMessage(msg);
}
}
public MainWindowViewModel()
{
SortingString = "Name~asc, Score~desc";
Students = StudentRepository.GetStudentList();
DoSorting();
WireCommands();
}
}
}
This view model class implements two properties and one "command":
- The "
Students
" property will be bound to the UI to display the sorted list of the students. - The "
SortingString
" property will be bound to the UI to get the sorting string from the user of the application. - The command "
DoSortingCommand
" will be bound to a "Button
" on the UI to initiate the sorting.
The sorting string
in this example will be a comma separated string like "Name~asc, Score~desc
". This string
tells us to sort the list of the students by the "Name
" field in ascending order and then by the "Score
" field in descending order. The "DoSorting
" method does the actual work using the sorting utility class introduced earlier.
- It first translates the sorting
string
into a list of tuples required by the sorting utility; - It then calls the extension method "
MultipleSort
" to sort the list of the students; - At last, it updates the "
Students
" property with the sorted list in the view model, so the sorted student
list is visible to the user through MVVM data binding.
In the following section, I will show you how the view model obtains the sorting string
and receives the sorting command from the UI. Now let's take a look at the "XAML" view of this example application.
The "XAML" View
The "XAML" view of the WPF application is implemented in the "MainWindow.xaml" file:
<Window x:Class="DynamicLinqMultipleSort.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Icon="Images\tiger.png" Style="{StaticResource WindowStyle}"
Title="Dynamic multiple sorting by Linq" Height="350" Width="525">
<Window.DataContext>
<Binding Source="{StaticResource MainWindowViewModel}" />
</Window.DataContext>
<Grid>
<Grid Margin="5">
<Grid.RowDefinitions>
<RowDefinition Height="auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Border Grid.Row="0" CornerRadius="5" Padding="5, 5, 5, 10"
BorderBrush="LightBlue" BorderThickness="2">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="auto" />
<RowDefinition Height="auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="auto" />
</Grid.ColumnDefinitions>
<TextBlock Grid.Row="0" Grid.ColumnSpan="2" FontWeight="Bold">
Please type in the sorting string</TextBlock>
<TextBox Grid.Column="0" Grid.Row="1"
Text="{Binding Path=SortingString}"
HorizontalAlignment="Stretch" BorderBrush="LightBlue"
Margin="0, 0, 5, 0" />
<Button Grid.Column="1" Grid.Row="1" Content="Apply Sorting" Width="120"
Command="{Binding Path=DoSortingCommand}" />
</Grid>
</Border>
<Border Grid.Row="1" CornerRadius="5" Padding="5, 5, 5, 10"
Margin="0, 5, 0, 5" BorderBrush="LightBlue" BorderThickness="2">
<DataGrid AutoGenerateColumns="False"
IsReadOnly="True" CanUserSortColumns="False"
ItemsSource="{Binding Path=Students, Mode=OneWay}">
<DataGrid.Resources>
<Style TargetType="{x:Type DataGridColumnHeader}">
<Setter Property="Height" Value="30" />
</Style>
</DataGrid.Resources>
<DataGrid.Columns>
<DataGridTextColumn Header="Id" Binding="{Binding Path=Id}" Width="30" />
<DataGridTextColumn Header="Name"
Binding="{Binding Path=Name}" Width="150" />
<DataGridTextColumn Header="Enrollment"
Binding="{Binding Path=Enrollment, StringFormat=MMM-dd-yyyy}"
Width="200" />
<DataGridTextColumn Header="Score"
Binding="{Binding Path=Score}" Width="50" />
</DataGrid.Columns>
</DataGrid>
</Border>
</Grid>
<Grid Visibility="{Binding Path=MessageVisibility}">
<Rectangle Fill="Black" Opacity="0.08" />
<Border BorderBrush="blue"
BorderThickness="1" CornerRadius="10"
Background="White"
HorizontalAlignment="Center" VerticalAlignment="Center">
<Grid Margin="10">
<Grid.RowDefinitions>
<RowDefinition Height="auto" />
<RowDefinition Height="35" />
</Grid.RowDefinitions>
<TextBlock Text="{Binding Path=Message, Mode=OneWay}"
MinWidth="150"
MaxWidth="300"
MinHeight="30"
TextWrapping="Wrap" Grid.Row="0" Margin="10, 5, 10, 5" />
<Button Content="OK" Grid.Row="1"
Margin="5" Width="100"
Command="{Binding Path=HideMessageCommand}"/>
</Grid>
</Border>
</Grid>
</Grid>
</Window>
The "XAML" view uses MVVM data binding to communicate to the view model.
- The "
Students
" property in the view model is bound to a "DataGrid" to display the sorted student list; - The "
SortingString
" property in the view model is bound to a "TextBox" to get the sorting string from the user; - The "
DoSortingCommand
" command in the view model is bound to a "Button" for the user to initiate the sorting.
Run the Application
Now we complete the demo application on how to use the sorting utility class. We can test run it. When the WPF application first launches, the default sorting string
and the list of students sorted by the default sorting string
is shown in the application window.
If we make some changes to the sorting string
and click the "Apply Sorting" button, the list of the students are re-ordered by the new sorting string
.
If we make any mistakes in the sorting string
and click on the "Apply Sorting" button, a modal dialog box is shown telling us to correct the sorting string
and try it again.
Points of Interest
- This article presented a small utility class to sort an "
IEnumerable
" of objects on multiple fields by the field names provided at run time using Linq. It also presented a WPF example on how to use this utility. - When multiple fields are used for sorting, the first field will be the primary sorting field, the following fields will be used in the sorting in the "ThenBy" style.
- This article is a simple extension of a "stackoverflow" discussion. If you are interested, you can take a look at it.
- I hope you like my postings and I hope this article can help you one way or the other.
History
- First revision - 11/10/2011