前言

现在这边公司有着自己的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.


随意游世