如何在Lambda表达式中获取属性名以消除IQueryable排序代码冗余
核心实现思路
IQueryable的排序方法要求传入强类型的表达式树Expression<Func<TSource, TKey>>,直接在Lambda中写反射逻辑无法被EF等查询提供者解析,也无法动态适配不同属性的返回类型,所以需要通过动态构建表达式树的方式实现通用排序。
针对当前场景的改造代码
using System; using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; using System.Reflection; namespace TestRedundance { public class EntityRedundance { public class Entity { public int Id { get; set; } public string Name { get; set; } public string Description { get; set; } public DateTime ValidFrom { get; set; } } private void AddSorting(IQueryable<Entity> query, List<string> sortingColumns, bool orderDescending) { if (!sortingColumns.Any()) { query = query.OrderBy(x => x.Name); return; } // 区分首次排序和后续排序,修复原代码多列排序仅最后一列生效的问题 bool isFirstSort = true; foreach (var column in sortingColumns) { // 校验属性是否存在,避免非法列名报错 PropertyInfo prop = typeof(Entity).GetProperty(column, BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase); if (prop == null) { // 可根据业务需求调整为抛异常或走默认逻辑 continue; } // 动态构建排序表达式 x => x.[属性名] ParameterExpression param = Expression.Parameter(typeof(Entity), "x"); MemberExpression propAccess = Expression.MakeMemberAccess(param, prop); LambdaExpression orderExpr = Expression.Lambda(propAccess, param); // 选择对应的排序方法 string sortMethod = isFirstSort ? (orderDescending ? "OrderByDescending" : "OrderBy") : (orderDescending ? "ThenByDescending" : "ThenBy"); // 反射调用排序方法 MethodInfo method = typeof(Queryable).GetMethods() .First(m => m.Name == sortMethod && m.GetParameters().Length == 2) .MakeGenericMethod(typeof(Entity), prop.PropertyType); query = (IQueryable<Entity>)method.Invoke(null, new object[] { query, orderExpr }); isFirstSort = false; } } } }
全项目通用泛型扩展方案
可以把排序逻辑抽成通用扩展方法,给任意实体类使用,不需要重复编写排序逻辑:
public static class QueryableSortExtensions { public static IQueryable<T> AddDynamicSort<T>(this IQueryable<T> query, List<string> sortColumns, bool isDescending, string defaultSortColumn = "Id") { if (!sortColumns.Any()) { // 处理默认排序逻辑 var defaultProp = typeof(T).GetProperty(defaultSortColumn, BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase); if (defaultProp == null) return query; var param = Expression.Parameter(typeof(T), "x"); var propAccess = Expression.MakeMemberAccess(param, defaultProp); var lambda = Expression.Lambda<Func<T, object>>(Expression.Convert(propAccess, typeof(object)), param); return isDescending ? query.OrderByDescending(lambda) : query.OrderBy(lambda); } bool isFirstSort = true; foreach (var column in sortColumns) { PropertyInfo prop = typeof(T).GetProperty(column, BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase); if (prop == null) continue; ParameterExpression param = Expression.Parameter(typeof(T), "x"); MemberExpression propAccess = Expression.MakeMemberAccess(param, prop); LambdaExpression orderExpr = Expression.Lambda(propAccess, param); string sortMethod = isFirstSort ? (isDescending ? "OrderByDescending" : "OrderBy") : (isDescending ? "ThenByDescending" : "ThenBy"); MethodInfo method = typeof(Queryable).GetMethods() .First(m => m.Name == sortMethod && m.GetParameters().Length == 2) .MakeGenericMethod(typeof(T), prop.PropertyType); query = (IQueryable<T>)method.Invoke(null, new object[] { query, orderExpr }); isFirstSort = false; } return query; } }
调用方式非常简单:
query = query.AddDynamicSort(sortingColumns, orderDescending, "Name");
优化点说明
- 完全消除了switch分支冗余,后续新增实体属性不需要修改排序逻辑
- 修复了原代码多列排序仅最后一列生效的问题,区分
OrderBy和ThenBy实现多字段排序 - 增加了属性合法性校验,避免传入非法列名导致程序崩溃
- 泛型扩展方案支持所有实体类复用,进一步减少重复代码
内容的提问来源于stack exchange,提问作者Guilherme Alves
相关产品推荐
相关产品推荐

