前言
现在这边公司有着自己的ORM,对OrderBy函数的设计较为复杂
使用起来的方式是这样的:
queryOrder = query.OrderBy(x => new object[] { SqlHelper.Desc(() => x.Id) });
虽说“自定义性”比较高,但对于一个存在多个排序字段,并支持正倒序的Api,使用这个方式,代码可能就变成这个样子了
var isAsc = input.Asc == 0;
var queryOrder = query.OrderBy(x => new object[] { SqlOrder.Desc(() => x.ID) });
switch (input.OrderBy)
{
case 1:
queryOrder = isAsc
? query.OrderBy(x => new object[] { SqlOrder.Asc(() => x.ID) })
: query.OrderBy(x => new object[] { SqlOrder.Desc(() => x.ID) }); break;
case 2:
queryOrder = isAsc
? query.OrderBy(x => new object[] { SqlOrder.Asc(() => x.ID) })
: query.OrderBy(x => new object[] { SqlOrder.Desc(() => x.ID) }); break;
}
这让人多少感觉少了些优雅
比如看看List的Order
list.OrderBy(x => x.Id).ToList();
这样就很有C#的优雅味了
所以就按照这个形式,在原本的ORM上套一层方法,来达到似乎使用Linq的方式来实现这个排序函数
1.首先,先看下ORM接受的是什么格式的参数,表达式是怎样的
ORM.OrderBy函数传入的是Expression<Func<T, object[]>>
组成表达式的内容是{x => new [] {Convert(Desc(() => x.Id), Object)}}
这个看着就难受,一个obj的数组,里面还套一个函数,函数里面使用了外部的变量
使用之前Update的那种转换思路肯定不行的了,这里需要将Expression<Func<T, object>>
构造成Expression<Func<T, object[]>>
按照这个思路,就比较清晰了,使用原始的Lambda构造方式去组装出想要的表达式到ORM即可
2.按照ORM的表达式,进行拆解,可以得到以下几个部分
() => x.Id
Convert(Desc(() => x.Id), Object)
new[] {}
x = > new[] {}
3.从里到外,先构造表达式x
变量
var parameterExpression = Expression.Parameter(typeof(T), "x");
4.构造Desc
函数
4.1获取并构造外部表达式传进来的参数
var expr2 = Expression.Property(parameterExpression, memberExpression.Member.Name);
结果:x.Id
4.2构造出Desc
使用的表达式
var sqlHelperExpression = Expression.Lambda(expr2);
结果:() => x.Id
5获取静态函数,并构造其泛型表达式
5.1获取函数信息 :var orderMethod = typeof(SqlHelper).GetMethod("Desc");
5.2构造泛型函数信息 :var orderGenericMethod = orderMethod.MakeGenericMethod(expr2.Type);
5.3构造调用表达式
var callExpr = Expression.Call(orderGenericMethod, sqlHelperExpression);
结果:Desc(() => x.Id)
6.构造object
数组,并把Desc函数丢进参数中
6.1因为要的结果类型是object[]
,类型与Desc不一样,使用Expression.Convert
套上Convert()
var newArrayExpression = Expression.NewArrayInit(typeof(object), Expression.Convert(callExpr, typeof(object)));
结果:new[] { Convert(Desc(() => x.Id), Object)}
6.2套上泛型变量,完成表达式组装
return Expression.Lambda<Func<T, object[]>>(newArrayExpression, parameterExpression);
完整代码:
表达式组装
public static Expression<Func<T, object[]>> OrderToOrmExp2<T>(Expression<Func<T, object>> ex, bool isAsc)
{
if (ex.NodeType != ExpressionType.Lambda)
throw new NotSupportedException($"Not Supported Order Expression NodeType,NodeType:{ex.NodeType}");
if (ex.Body == null || ex.Body.NodeType != ExpressionType.Convert)
throw new NotSupportedException($"Not Supported order expression formula or expression.body is Null,NodeType:{ex.NodeType}");
var unaryExpression = (UnaryExpression)ex.Body;
if (unaryExpression == null)
throw new NotSupportedException($"Not Supported UnaryExpression formula,Expression:{ex}");
var memberExpression = (MemberExpression)unaryExpression.Operand;
if (memberExpression == null)
throw new NotSupportedException($"Not Supported MemberExpression formula,Expression:{ex}");
//表达式实体临时变量
var parameterExpression = Expression.Parameter(typeof(T), "x");
//SqlOrder的表达式内容
var expr2 = Expression.Property(parameterExpression, memberExpression.Member.Name);
var sqlHelperExpression = Expression.Lambda(expr2);
//创建SqlOrder函数表达式
var orderMethodName = isAsc ? nameof(SqlOrder.Asc) : nameof(SqlOrder.Desc);
var orderMethod = typeof(SqlOrder).GetMethod(orderMethodName);
var orderGenericMethod = orderMethod.MakeGenericMethod(expr2.Type);
var callExpr = Expression.Call(orderGenericMethod, sqlHelperExpression);
//构建ORM用的排序表达式
var newArrayExpression = Expression.NewArrayInit(typeof(object), Expression.Convert(callExpr, typeof(object)));
var funcCompiled = Expression.Lambda<Func<T, object[]>>(newArrayExpression, parameterExpression);
return funcCompiled;
}
结果
虽然这个实现不难,但对于习惯了 CURD ,较少接触Expression组装的业务仔来说,确实不是很熟悉,特别是Expression
大量各种函数
虽说暂时只能靠直觉去使用,不过起码用过后学到了一种使用的直觉,不像初学那样一脸懵逼,连资料都不知道怎么去查
完成这个函数后,就可以在业务代码优雅地使用OrderBy了
var isAsc = input.Asc == 0;
var queryOrder = query.OrderByModel(x => x.ID, isAsc);
switch (input.OrderBy)
{
case 1: queryOrder = query.OrderByModel(x => x.LabelID, isAsc); break;
case 2: queryOrder = query.OrderByModel(x => x.AddTime, isAsc); break;
}
Q.E.D.