实现较简单的OrderBy使用方式,适配使用起来表达式比较复杂的ORM

_

前言

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

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;
}

FW 迁移到 Core 常见的坑或问题 2022-06-07
排查一次线程数与预想数不一样的问题 - 线程池 2023-02-17