LINQ中的多重排序

LINQ中的多重排序

技术背景

在使用LINQ(Language Integrated Query)进行数据库查询时,经常会遇到需要对查询结果进行多重排序的需求。多重排序可以按照多个字段对结果集进行排序,以满足特定的业务需求。

实现步骤

1. 使用Lambda表达式

1
var movies = _db.Movies.OrderBy(c => c.Category).ThenBy(n => n.Name);

上述代码先按照Category字段排序,再按照Name字段排序。

2. 使用查询语法

1
2
3
var movies = from row in _db.Movies 
orderby row.Category, row.Name
select row;

3. 控制排序顺序

可以使用ascending(默认)或descending关键字来控制排序顺序。

1
2
3
var movies = from row in _db.Movies 
orderby row.Category descending, row.Name
select row;

4. 使用new关键字

1
var movies = _db.Movies.OrderBy( m => new { m.CategoryID, m.Name });

5. 按组合列排序

1
var movies = _db.Movies.OrderBy( m => (m.CategoryID.ToString() + m.Name));

6. 记录SQL活动

可以使用以下代码将DataContext的SQL活动记录到控制台,以便查看LINQ语句向数据库请求的具体内容。

1
_db.Log = Console.Out;

7. 重复OrderBy的影响

重复使用OrderBy会反转SQL输出的排序结果。

1
2
3
4
5
6
var movies = from row in _db.Movies 
orderby row.CategoryID
orderby row.Name
select row;

var movies = _db.Movies.OrderBy(m => m.CategoryID).OrderBy(m => m.Name);

8. 使用扩展方法

创建扩展方法可以处理IQueryable是否已经排序的情况。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
public static class IQueryableExtension
{
public static bool IsOrdered<T>(this IQueryable<T> queryable) {
if(queryable == null) {
throw new ArgumentNullException("queryable");
}

return queryable.Expression.Type == typeof(IOrderedQueryable<T>);
}

public static IQueryable<T> SmartOrderBy<T, TKey>(this IQueryable<T> queryable, Expression<Func<T, TKey>> keySelector) {
if(queryable.IsOrdered()) {
var orderedQuery = queryable as IOrderedQueryable<T>;
return orderedQuery.ThenBy(keySelector);
} else {
return queryable.OrderBy(keySelector);
}
}

public static IQueryable<T> SmartOrderByDescending<T, TKey>(this IQueryable<T> queryable, Expression<Func<T, TKey>> keySelector) {
if(queryable.IsOrdered()) {
var orderedQuery = queryable as IOrderedQueryable<T>;
return orderedQuery.ThenByDescending(keySelector);
} else {
return queryable.OrderByDescending(keySelector);
}
}
}

// 使用示例
queryable.SmartOrderBy(i => i.Property1).SmartOrderByDescending(i => i.Property2);

9. 使用IComparer

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class MovieComparer : IComparer<Movie>
{
public int Compare(Movie x, Movie y)
{
if (x.CategoryId == y.CategoryId)
{
return x.Name.CompareTo(y.Name);
}
else
{
return x.CategoryId.CompareTo(y.CategoryId);
}
}
}

var movies = _db.Movies.OrderBy(item => item, new MovieComparer());

10. 使用通用仓储

1
lstModule = _ModuleRepository.GetAll().OrderBy(x => new { x.Level, x.Rank}).ToList();

或者

1
_db.Module.Where(x => ......).OrderBy(x => new { x.Level, x.Rank}).ToList();

核心代码

以下是扩展方法和IComparer实现的完整代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
// 扩展方法
public static class IQueryableExtension
{
public static bool IsOrdered<T>(this IQueryable<T> queryable) {
if(queryable == null) {
throw new ArgumentNullException("queryable");
}

return queryable.Expression.Type == typeof(IOrderedQueryable<T>);
}

public static IQueryable<T> SmartOrderBy<T, TKey>(this IQueryable<T> queryable, Expression<Func<T, TKey>> keySelector) {
if(queryable.IsOrdered()) {
var orderedQuery = queryable as IOrderedQueryable<T>;
return orderedQuery.ThenBy(keySelector);
} else {
return queryable.OrderBy(keySelector);
}
}

public static IQueryable<T> SmartOrderByDescending<T, TKey>(this IQueryable<T> queryable, Expression<Func<T, TKey>> keySelector) {
if(queryable.IsOrdered()) {
var orderedQuery = queryable as IOrderedQueryable<T>;
return orderedQuery.ThenByDescending(keySelector);
} else {
return queryable.OrderByDescending(keySelector);
}
}
}

// IComparer实现
public class MovieComparer : IComparer<Movie>
{
public int Compare(Movie x, Movie y)
{
if (x.CategoryId == y.CategoryId)
{
return x.Name.CompareTo(y.Name);
}
else
{
return x.CategoryId.CompareTo(y.CategoryId);
}
}
}

最佳实践

  • 当需要进行多重排序时,优先使用OrderByThenBy组合,避免重复使用OrderBy导致排序结果不符合预期。
  • 如果需要动态生成排序规则,使用扩展方法可以简化代码逻辑。
  • 使用IComparer可以实现更复杂的排序逻辑。

常见问题

  • 重复使用OrderBy导致排序结果异常:重复使用OrderBy会反转SQL输出的排序结果,应使用OrderByThenBy组合。
  • 忘记处理IQueryable是否已经排序的情况:使用扩展方法可以避免这个问题。