Click here to Skip to main content
15,883,940 members
Articles / Desktop Programming / WPF

Multiple Field Sorting by Field Names Using Linq

Rate me:
Please Sign up or sign in to vote.
4.27/5 (3 votes)
10 Nov 2011CPOL6 min read 65K   489   9   11
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.

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.

SolutionExplorer.jpg

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:

C#
using System;
using System.Collections.Generic;
using System.Linq;
 
namespace DynamicLinqMultipleSort.Utilities
{
   public static class LinqDynamicMultiSortingUtility
   {
     /// <summary>
     /// 1. The sortExpressions is a list of Tuples, the first item of the 
     ///    tuples is the field name,
     ///    the second item of the tuples is the sorting order (asc/desc) case sensitive.
     /// 2. If the field name (case sensitive) provided for sorting does not exist 
     ///    in the object,
     ///    exception is thrown
     /// 3. If a property name shows up more than once in the "sortExpressions", 
     ///    only the first takes effect.
     /// </summary>
     /// <typeparam name="T"></typeparam>
     /// <param name="data"></param>
     /// <param name="sortExpressions"></param>
     /// <returns></returns>
     public static IEnumerable<T> MultipleSort<T>(this IEnumerable<T> data,
       List<Tuple<string, string>> sortExpressions)
     {
       // No sorting needed
       if ((sortExpressions == null) || (sortExpressions.Count <= 0))
       {
         return data;
       }
 
       // Let us sort it
       IEnumerable<T> query = from item in data select item;
       IOrderedEnumerable<T> orderedQuery = null;
 
       for (int i = 0; i < sortExpressions.Count; i++)
       {
         // We need to keep the loop index, not sure why it is altered by the Linq.
         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:

C#
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()
     {
       // Make sure the same student list is returned
       if (Students != null)
       {
         return Students;
       }
 
       // If no previously created list, make a new one and fill in data
       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:

C#
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
   {
     // Properties
     // 1. The students list
     private List<Student> students;
     public List<Student> Students
     {
       get { return students; }
       set
       {
         if (students != value)
         {
           students = value;
           NotifyPropertyChanged("Students");
         }
       }
     }
 
     // 2. The sorting string
     private string sortingString;
     public string SortingString
     {
       get { return sortingString; }
       set
       {
         if (sortingString != value)
         {
           sortingString = value;
           NotifyPropertyChanged("SortingString");
         }
       }
     }
 
     // Command
     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))
       {
         // If no sorting string, give a message and return.
         ShowMessage("Please type in a sorting string.");
         return;
       }
 
       try
       {
         // Prepare the sorting string into a list of Tuples
         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));
         }
 
         // Apply the sorting
         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);
       }
     }
 
     // Constructor
     public MainWindowViewModel()
     {
       // Create Default sorting
       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:

XML
<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.

RunAppStart.jpg

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.

RunAppSort.jpg

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.

RunAppError.jpg

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

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
United States United States
I have been working in the IT industry for some time. It is still exciting and I am still learning. I am a happy and honest person, and I want to be your friend.

Comments and Discussions

 
QuestionNice, but there is a better alternative Pin
aliascodeproject10-Nov-11 10:55
aliascodeproject10-Nov-11 10:55 
AnswerRe: Nice, but there is a better alternative Pin
Dr. Song Li10-Nov-11 11:35
Dr. Song Li10-Nov-11 11:35 
GeneralMy vote of 4 Pin
Paulo Zemek10-Nov-11 9:49
mvaPaulo Zemek10-Nov-11 9:49 
GeneralRe: My vote of 4 Pin
Dr. Song Li10-Nov-11 10:01
Dr. Song Li10-Nov-11 10:01 
GeneralRe: My vote of 4 Pin
Paulo Zemek10-Nov-11 10:05
mvaPaulo Zemek10-Nov-11 10:05 
GeneralRe: My vote of 4 Pin
Dr. Song Li10-Nov-11 11:42
Dr. Song Li10-Nov-11 11:42 
GeneralRe: My vote of 4 Pin
Dr. Song Li10-Nov-11 12:35
Dr. Song Li10-Nov-11 12:35 
GeneralRe: My vote of 4 Pin
Richard Deeming15-Nov-11 8:12
mveRichard Deeming15-Nov-11 8:12 
GeneralRe: My vote of 4 Pin
Dr. Song Li15-Nov-11 8:19
Dr. Song Li15-Nov-11 8:19 
GeneralRe: My vote of 4 Pin
Richard Deeming15-Nov-11 8:37
mveRichard Deeming15-Nov-11 8:37 
GeneralRe: My vote of 4 Pin
Dr. Song Li15-Nov-11 8:43
Dr. Song Li15-Nov-11 8:43 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.