使用 Linq 按字段名进行多字段排序
本文介绍了一个小型实用工具类,该类可使用 Linq 按运行时提供的字段名对对象的“IEnumerable”进行多字段排序。它还提供了一个 WPF 示例,说明如何使用此实用工具。
引言
本文介绍了一个小型实用工具类,该类可使用 Linq 按运行时提供的字段名对对象的“IEnumerable
”进行多字段排序。它还提供了一个 WPF 示例,说明如何使用此实用工具。
背景
语言集成查询“Linq”提供了许多编程支持。 此链接是学习如何使用“Linq”的好地方。在这些编程支持中,排序是“Linq”的不错功能之一。但问题是,我们通常在编译时不知道要排序的字段,因此我认为需要一个实用工具来扩展“Linq”的排序功能,以满足两个要求:
- 该实用工具应支持多字段排序;
- 该实用工具应能够不使用复杂的“switch 语句”而在运行时获取字段名和这些字段的排序顺序。
本文将该实用工具呈现为一个静态类中的“扩展方法”。它允许我们在运行时按任意字段名对对象的“IEnumerable”进行多字段排序。尽管本文使用 WPF MVVM 应用程序来展示如何使用该实用工具类,但您不必具备大量 MVVM 和 WPF 知识即可阅读本文。该实用工具类非常易于使用,因此如果您对 MVVM 和 WPF 的知识不足,可以跳过该示例。
随附的 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
排序的学生列表。
如果我们更改排序 string
并单击“应用排序”按钮,学生列表将按新的排序 string
重新排序。
如果我们对排序 string
进行任何错误操作并单击“应用排序”按钮,将显示一个模态对话框,告知我们更正排序 string
后重试。
关注点
- 本文介绍了一个小型实用工具类,该类可使用 Linq 按运行时提供的字段名对对象的“
IEnumerable
”进行多字段排序。它还介绍了一个 WPF 示例,说明如何使用此实用工具。 - 当使用多个字段进行排序时,第一个字段将是主要排序字段,后续字段将以“ThenBy”样式用于排序。
- 本文是对一次“stackoverflow”讨论的一个简单扩展。如果您有兴趣,可以看一下。
- 我希望您喜欢我的文章,希望本文能以某种方式帮助您。
历史
- 首次修订 - 2011/10/11