65.9K
CodeProject 正在变化。 阅读更多。
Home

使用 Linq 按字段名进行多字段排序

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.27/5 (3投票s)

2011年11月10日

CPOL

6分钟阅读

viewsIcon

67380

downloadIcon

505

本文介绍了一个小型实用工具类,该类可使用 Linq 按运行时提供的字段名对对象的“IEnumerable”进行多字段排序。它还提供了一个 WPF 示例,说明如何使用此实用工具。

引言

本文介绍了一个小型实用工具类,该类可使用 Linq 按运行时提供的字段名对对象的“IEnumerable”进行多字段排序。它还提供了一个 WPF 示例,说明如何使用此实用工具。

背景

语言集成查询“Linq”提供了许多编程支持。 此链接是学习如何使用“Linq”的好地方。在这些编程支持中,排序是“Linq”的不错功能之一。但问题是,我们通常在编译时不知道要排序的字段,因此我认为需要一个实用工具来扩展“Linq”的排序功能,以满足两个要求:

  • 该实用工具应支持多字段排序;
  • 该实用工具应能够不使用复杂的“switch 语句”而在运行时获取字段名和这些字段的排序顺序。

本文将该实用工具呈现为一个静态类中的“扩展方法”。它允许我们在运行时按任意字段名对对象的“IEnumerable”进行多字段排序。尽管本文使用 WPF MVVM 应用程序来展示如何使用该实用工具类,但您不必具备大量 MVVM 和 WPF 知识即可阅读本文。该实用工具类非常易于使用,因此如果您对 MVVM 和 WPF 的知识不足,可以跳过该示例。

SolutionExplorer.jpg

随附的 Visual Studio 2010 解决方案是一个 WPF “MVVM”项目。此 WPF 项目中的主要组件如下:

  • Utilities”文件夹中的“DynamicLinqMultiSortingUtility.cs”文件实现了排序实用工具类。
  • Models”文件夹中的“StudentRepository.cs”文件实现了应用程序的数据模型。
  • ViewModels”文件夹中的“MainWindowViewModel.cs”文件实现了应用程序的视图模型。
  • MainWindow.xaml”文件是演示应用程序的 XAML 视图。

我将首先介绍排序实用工具类,然后展示如何在 WPF 应用程序中使用它。

排序实用工具类

排序实用工具类在“Utilities”文件夹中的“DynamicLinqMultiSortingUtility.cs”文件中实现。

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

MultipleSort扩展方法是我们用来排序“IEnumerable”对象的类。

  • sortExpressions”参数是一个元组列表,每个元组的第一个项是字段名,第二个项是排序顺序(asc/desc)。字段名和排序顺序都区分大小写。
  • 如果提供的排序字段名(区分大小写)在对象中不存在,则会抛出异常。
  • 如果属性名在“sortExpressions”列表中出现多次,只有第一次有效。
  • 如果提供了多个字段名进行排序,则第一个字段将是主要排序字段,后续字段将以“ThenBy”样式用于排序。

如果一切顺利,则从方法中返回已排序的“IEnumerable”对象。在本文的后续部分,我将展示如何在 WPF 应用程序中使用此实用工具类。

数据模型

为了展示如何使用该实用工具类,我需要生成一个“IEnumerable”对象作为数据模型。演示应用程序的数据模型在“Models”文件夹的“StudentRepository.cs”文件中实现。

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

StudentRepository”类中的“GetStudentList”方法随机生成一个“Student”对象的“List”。具体的“List”类实现了“IEnumerable”接口,因此我们可以在示例中使用此学生列表。我将展示如何使用本文介绍的实用工具类对该学生列表进行排序。

ViewModel

演示 WPF 应用程序的视图模型在“ViewModels”文件夹的“MainWindowViewModel.cs”文件中实现。

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

该视图模型类实现了两个属性和一个“命令”。

  • Students”属性将绑定到 UI,以显示已排序的学生列表。
  • SortingString”属性将绑定到 UI,以获取应用程序用户输入的排序字符串。
  • 命令“DoSortingCommand”将绑定到 UI 上的“Button”,以启动排序。

在此示例中,排序 string 将是一个逗号分隔的字符串,如“Name~asc, Score~desc”。此 string 告诉我们按“Name”字段升序排序学生列表,然后按“Score”字段降序排序。 “DoSorting”方法使用前面介绍的排序实用工具类执行实际工作。

  • 它首先将排序 string 转换为排序实用工具所需的元组列表;
  • 然后,它调用扩展方法“MultipleSort”对学生列表进行排序;
  • 最后,它使用已排序列表更新视图模型中的“Students”属性,因此用户可以通过 MVVM 数据绑定看到已排序的 student 列表。

在下一节中,我将展示视图模型如何获取排序 string 并从 UI 接收排序命令。现在,让我们看一下此示例应用程序的“XAML”视图。

XAML 视图

WPF 应用程序的“XAML”视图在“MainWindow.xaml”文件中实现。

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

“XAML”视图使用 MVVM 数据绑定与视图模型进行通信。

  • 视图模型中的“Students”属性将绑定到一个“DataGrid”,以显示已排序的学生列表;
  • 视图模型中的“SortingString”属性将绑定到一个“TextBox”,以从用户那里获取排序字符串;
  • 视图模型中的“DoSortingCommand”命令将绑定到一个“Button”,供用户启动排序。

运行应用程序

现在,我们完成了关于如何使用排序实用工具类的演示应用程序。我们可以对其进行测试运行。当 WPF 应用程序首次启动时,应用程序窗口将显示默认排序 string 以及按默认排序 string 排序的学生列表。

RunAppStart.jpg

如果我们更改排序 string 并单击“应用排序”按钮,学生列表将按新的排序 string 重新排序。

RunAppSort.jpg

如果我们对排序 string 进行任何错误操作并单击“应用排序”按钮,将显示一个模态对话框,告知我们更正排序 string 后重试。

RunAppError.jpg

关注点

历史

  • 首次修订 - 2011/10/11
© . All rights reserved.