剑轩

表达式树+反射扩展EF实现动态排序

电脑版发表于:2019/10/23 9:22

我们在显示表格的时候经常会在点击表头的时候实现排序,当然很多前端的框架都实现了当前页的页面排序,直接配置一下就行了,但是我们

这里要说的是全表排序,也就是前台需要传递排序字段(根据什么排序)和排序方式(升序或者降序)。


我们最容易想到的做法就是使用判断来,根据排序字段来判断根据什么排序

if (sort.ToLower() == "max")
{
    if (sortway == "asc")
    {
        query = query.OrderBy(a => a.Max);
    }
    else
    {
        query = query.OrderByDescending(a => a.Max);
    }
}
if (sort.ToLower() == "min")
{
    if (sortway == "asc")
    {
        query = query.OrderBy(a => a.Min);
    }
    else
    {
        query = query.OrderByDescending(a => a.Min);
    }
}
if (sort.ToLower() == "sum")
{
    if (sortway == "asc")
    {
        query = query.OrderBy(a => a.Min);
    }
    else
    {
        query = query.OrderByDescending(a => a.Min);
    }
}

但是这样判断就太多了,如果其他地方也要用排序重复代码就会很多


我们可以发现其实排序字段和里边的a=>a.Max,a=>a.Min右边都是一样的,如果我们可以动态的构建一个lamdba式就好了。

其实是可以的,我们使用表达式树Expression就可以了

如下所示我们就可以根据一个字符串动态生成了一个a=a.Max的lamdba,在稍微封装一下代码

if (!string.IsNullOrEmpty(sort))
{
    var left = Expression.Parameter(typeof(ScoreViewModel), "a");
    var body = Expression.Property(left, sort);
    Expression<Func<ScoreViewModel, double?>> lamdba = Expression.Lambda<Func<ScoreViewModel, double?>>(body, left);=
    if (sortway == "asc")
    {
        query = query.OrderBy(lamdba);
    }
    else
    {
        query = query.OrderByDescending(lamdba);
    }
}

这样就可以实现动态排序了


根据最低分排序

但是现在还有2个问题要解决

1:把动态排序通过扩展方法的方式把ef扩展一下,可以直接用在ef上面

    这个问题还是很好解决,写个扩展方法封装一下就好了

public static IQueryable<TSource> OrderBy<TSource>(this IQueryable<TSource> source, string sort, string sortway) 
{
    if (!string.IsNullOrEmpty(sort))
    {
        var left = Expression.Parameter(typeof(TSource), "a");
        var body = Expression.Property(left, sort);
        Expression<Func<TSource, int?>> lamdba = Expression.Lambda<Func<TSource, int?>>(body, left);
        if (sortway == "asc")
        {
            source = source.OrderBy(lamdba);
        }
        else
        {
            source = source.OrderByDescending(lamdba);
        }
    }
    return source;
}

使用的时候很简单在后面直接点出来就好


2:我们现在写的动态排序只支持int类型,如果是小数类型排序就会报错,比如根据平均分排序

 所以我们这里不能写死,写死就不灵活了。这里就需要动态的获取属性类型,可以巧妙的使用反射方法来中转,因为反射调用方法的时候可以传递类型参数


第一步:要拿到排序字段的类型

//第一步要拿到排序字段的类型
Type propertyType = typeof(TSource).GetProperty(sort).PropertyType;

第二步:可以把方法提出来写,比如提出来一个处理升序的方法

/// <summary>
/// 处理升序排序
/// 通过一个方法中转实现类型的传递
/// </summary>
public static IQueryable<TSource> DealAsc<TSource, M>(IQueryable<TSource> query, string sort)
{
    var left = Expression.Parameter(typeof(TSource), "a");
    var body = Expression.Property(left, sort);
    Expression<Func<TSource, M>> lamdba = Expression.Lambda<Func<TSource, M>>(body, left);
    query = query.OrderBy(lamdba);
    return query;
}

注意:在这里就可以使用两个泛型了,这样动态构建的lamdba的类型就可以动态的来了


第二步:调用方法

这里如果直接调用也是没有办法传递类型的,第一步获取的类型无法提供给泛型

所以我们需要通过反射来调用

//第一步要拿到排序字段的类型
Type propertyType = typeof(TSource).GetProperty(sort).PropertyType;

//通过反射拿到方法
var method = typeof(MyEFTools).GetMethod("DealAsc");
//给反射拿到的方法提供泛型
method = method.MakeGenericMethod(typeof(TSource), propertyType);
//反射调用方法
IQueryable<TSource> result = (IQueryable<TSource>)method.Invoke(null, new object[] { query, sort });

这里最关键的一步就是

//给反射拿到的方法提供泛型
method = method.MakeGenericMethod(typeof(TSource), propertyType);

这样就可以巧妙的传递类型了


最后贴一点完整的代码:

public static class MyEFTools
{
    public static IQueryable<TSource> OrderBy<TSource>(this IQueryable<TSource> query, string sort, string sortway)
    {
        if (!string.IsNullOrEmpty(sort))
        {
            //第一步要拿到排序字段的类型
            Type propertyType = typeof(TSource).GetProperty(sort).PropertyType;
            //通过反射拿到方法
            var method = typeof(MyEFTools).GetMethod(sortway == "asc" ? "DealAsc" : "DealDesc");
            //给反射拿到的方法提供泛型
            method = method.MakeGenericMethod(typeof(TSource), propertyType);
            //反射调用方法
            IQueryable<TSource> result = (IQueryable<TSource>)method.Invoke(null, new object[] { query, sort });
            return result;
        }
        return query;
    }

    /// <summary>
    /// 处理升序排序
    /// 通过一个方法中转实现类型的传递
    /// </summary>
    public static IQueryable<TSource> DealAsc<TSource, M>(IQueryable<TSource> query, string sort)
    {
        var left = Expression.Parameter(typeof(TSource), "a");
        var body = Expression.Property(left, sort);
        Expression<Func<TSource, M>> lamdba = Expression.Lambda<Func<TSource, M>>(body, left);
        query = query.OrderBy(lamdba);
        return query;
    }


    /// <summary>
    /// 处理降序排序
    /// 通过一个方法中转实现类型的传递
    /// </summary>
    public static IQueryable<TSource> DealDesc<TSource, M>(IQueryable<TSource> query, string sort)
    {
        var left = Expression.Parameter(typeof(TSource), "a");
        var body = Expression.Property(left, sort);
        Expression<Func<TSource, M>> lamdba = Expression.Lambda<Func<TSource, M>>(body, left);
        query = query.OrderByDescending(lamdba);
        return query;
    }
}

最后的最后,当然我们可以继续优化一下代码,因为生成lamdba的代码重复了,我可以提一个方法出来

public static class MyEFTools
{
    public static IQueryable<TSource> OrderBy<TSource>(this IQueryable<TSource> query, string sort, string sortway)
    {
        if (!string.IsNullOrEmpty(sort))
        {
            //第一步要拿到排序字段的类型
            Type propertyType = typeof(TSource).GetProperty(sort).PropertyType;
            //通过反射拿到方法
            var method = typeof(MyEFTools).GetMethod(sortway == "asc" ? "DealAsc" : "DealDesc");
            //给反射拿到的方法提供泛型
            method = method.MakeGenericMethod(typeof(TSource), propertyType);
            //反射调用方法
            IQueryable<TSource> result = (IQueryable<TSource>)method.Invoke(null, new object[] { query, sort });
            return result;
        }
        return query;
    }

    /// <summary>
    /// 处理升序排序
    /// 通过一个方法中转实现类型的传递
    /// </summary>
    public static IQueryable<TSource> DealAsc<TSource, M>(IQueryable<TSource> query, string sort)
    {
        query = query.OrderBy(OrderLamdba<TSource, M>(query, sort));
        return query;
    }


    /// <summary>
    /// 处理降序排序
    /// 通过一个方法中转实现类型的传递
    /// </summary>
    public static IQueryable<TSource> DealDesc<TSource, M>(IQueryable<TSource> query, string sort)
    {
        query = query.OrderByDescending(OrderLamdba<TSource, M>(query, sort));
        return query;
    }

    static Expression<Func<TSource, M>> OrderLamdba<TSource, M>(IQueryable<TSource> query, string sort)
    {
        var left = Expression.Parameter(typeof(TSource), "a");
        var body = Expression.Property(left, sort);
        Expression<Func<TSource, M>> lamdba = Expression.Lambda<Func<TSource, M>>(body, left);
        return lamdba;
    }
}

还有那个三明运算符找处理方法的代码不想用判断也可以直接构建一个字符串,然后反射的时候忽略一下大小写

好了就说到这里了,现在扩展的这个排序方法可以不用纠结类型了,直接传递排序字段和排序方式即可!



关于TNBLOG
TNBLOG,技术分享
ICP备案 :渝ICP备18016597号-1
App store Android
精彩评论
{{item.replyName}}
{{item.content}}
{{item.time}}
{{subpj.replyName}}
@{{subpj.beReplyName}}{{subpj.content}}
{{subpj.time}}
猜你喜欢