C#之表达式树使用

目的

遇到一个场景需要接收一个表的列来进行动态排序,比如我想根据CreateTime进行正序排序,加上我使用的ORM框架是EFCore,那么我一下子就想到应该使用OrderBy,然后接收一个要排序的列

query.OrderBy("CreateTime")

但是这样子是不行的,所以寻找方案后找到了一个nuget包System.Linq.Dynamic.Core,该包的用法如下.

//OrderBy("time asc")
query.OrderBy($"{orderContent.SortName} {orderContent.Sort}");

但是把这样子就会多引用一个nuget包,所以就在继续找其他方法,就找到了使用表达式树来实现,那么就简单介绍几种表达式树可能用到的例子吧。

是什么

定义一种树状的数据结构来描述c#中的代码,这种树状的数据结构就是表达式树。

还是直接放上原文的链接吧

和委托的关系

表达式树其实与委托已经没什么关系了,非要扯上关系,那就这么说吧,表达式树是存放委托的容器。

要用Lambda表达式的时候,直接从表达式中获取出来,Compile()就可以直接用了。如下代码:

static void Main(string[] args)
{
    Expression<Func<int, int, int>> exp = (x, y) => x + y;
    Func<int, int, int> fun = exp.Compile();
    int result = fun(2, 3);
}

使用场景

平常我是很少或者几乎不自己构建表达式树,就像上面文章那个老哥说的那样子,表达式树一般是给框架的作者用的。

操作

下文中UserDto.GetUserDtos()返回的是一个UserDto集合(算是一个伪代码),虽然本文示例没有直接查询的数据库,但是我已经在其他项目中测试过,在执行输出SQL中已经体现出来效果。

本文示例虽已经过验证,但是还未上生产。

简单筛选

public void SampleWhere()
{
    var user = UserDto.GetUserDtos().AsQueryable();
    //泛型写法
    Func<UserDto, bool> predicate = s => s.Deleted=false;
    var list = user.Where(predicate).ToList(); // 这个执行是在内存中执行的

    //表达式树写法
    Expression<Func<UserDto, bool>> lambdaExp = (s) => !s.Deleted;
    var list2 = user.Where(lambdaExp).ToList();
}

通过简单的筛选去学习如何创建简单的表达式树

/// <summary>
/// 通过简单的筛选去学习
/// </summary>
public void SampleWhereToStudy()
{
    // 实现效果   t => t.Name == "张三"
    var user = UserDto.GetUserDtos().AsQueryable();
    var result1 = user.Where(t => t.Name == "张三");

    ParameterExpression demo = Expression.Parameter(typeof(UserDto), "t");
    Console.WriteLine(demo);

    MemberExpression demo_name = Expression.Property(demo, "Name");
    Console.WriteLine(demo_name);// t.Name

    ConstantExpression value = Expression.Constant("张三");
    Console.WriteLine(value);// 张三

    BinaryExpression greaterThen = Expression.Equal(demo_name, value);
    Console.WriteLine(greaterThen); // t.Name=="张三"

    var lambda = Expression.Lambda<Func<UserDto, bool>>(greaterThen, demo);
    Console.WriteLine(lambda);// t=>t.Name=="张三"

    var lamdbaFunc = lambda.Compile();// 编译表达式
    Console.WriteLine(lamdbaFunc);// System.Func`2[CSharpBasic.Model.UserDto,System.Boolean]

    var resultTrue = lamdbaFunc(new UserDto { Name = "张三" });
    Console.WriteLine(resultTrue);//True

    // 筛选张三
    var result = user.Where(lamdbaFunc).ToList();
}

资料来自:超超老师教程

动态筛选

public void DynamicWhere()
{
    //实现效果:已知一个表UserDto  包含属性Name、Address、Id等,需要实现通过属性进行动态过滤
    var user = UserDto.GetUserDtos().AsQueryable();
    var list2 = user.EqualWhere("Name", "张三").FirstOrDefault();
    // 执行SQL:SELECT u.id, u.account,u.name FROM test."user" AS u WHERE u.name = '张三'
}

// 扩展方法
public static class ExpressExtensons
{
    /// <summary>
    /// 等于筛选
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="queryable"></param>
    /// <param name="whereField"></param>
    /// <param name="value"></param>
    /// <returns></returns>
    public static IQueryable<T> EqualWhere<T>(this IQueryable<T> queryable, string whereField, object value)
    {
        return queryable.Where<T>(whereField, value);
    }

    /// <summary>
    /// 小于筛选
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="queryable"></param>
    /// <param name="whereField"></param>
    /// <param name="value"></param>
    /// <returns></returns>
    public static IQueryable<T> LessWhere<T>(this IQueryable<T> queryable, string whereField, object value)
    {
        return queryable.Where<T>(whereField, value, 1);
    }

    /// <summary>
    /// 大于筛选
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="queryable"></param>
    /// <param name="whereField"></param>
    /// <param name="value"></param>
    /// <returns></returns>
    public static IQueryable<T> GreaterWhere<T>(this IQueryable<T> queryable, string whereField, object value)
    {
        return queryable.Where<T>(whereField, value, 1);
    }

    private static IQueryable<T> Where<T>(this IQueryable<T> queryable, string whereField, object value, int type = 0)
    {
        var paramExp = Expression.Parameter(typeof(T), "t");
        //因为这个Property里面已经包含属性校验的功能,所以不用再另外写了
        var memberExp = Expression.Property(paramExp, whereField);
        //值表达式
        var valueExp = Expression.Constant(value);
        var exp = type switch
        {
            //小于
            1 => Expression.LessThan(memberExp, valueExp),
            //小于等于
            2 => Expression.LessThanOrEqual(memberExp, valueExp),
            //大于
            3 => Expression.GreaterThan(memberExp, valueExp),
            //大于等于
            4 => Expression.GreaterThanOrEqual(memberExp, valueExp),
            //等于
            _ => Expression.Equal(memberExp, valueExp),
        };
        var lambda = Expression.Lambda<Func<T, bool>>(exp, paramExp);

        return queryable.Where(lambda);
    }
}

动态排序

public void DynamicOrderby()
{
    // 实现效果
    var userQueryable = UserDto.GetUserDtos().AsQueryable();
    var sortList = userQueryable.OrderBy(t => t.CreateTime).ToList();
    foreach (var item in sortList)
    {
        Console.WriteLine(item.CreateTime);
    }

    Console.WriteLine("-----------------");

    // 通过表达式树去实现效果
    var list = userQueryable.OrderBy("CreateTime", false);
    // 执行SQL:SELECT u.id, u.account, u.create_time FROM test."user" AS u ORDER BY u.create_time DESC
    foreach (var item in list)
    {
        Console.WriteLine(item.CreateTime);
    }
}


public static class ExpressExtensons
{
    /// <summary>
    /// 根据字段排序处理
    /// </summary>
    /// <typeparam name="T">泛型列</typeparam>
    /// <param name="queryable">查询queryable</param>
    /// <param name="sortField">排序列</param>
    /// <param name="isAsc">true正序 false倒序</param>
    /// <returns></returns>
    public static IQueryable<T> OrderBy<T>(this IQueryable<T> queryable, string sortField, bool isAsc = true)
    {
        var parameter = Expression.Parameter(typeof(T));
        var property = typeof(T).GetProperty(sortField);
        if (property == null)
            throw new ArgumentNullException($"无效的属性 {sortField}");

        var memberExpression = Expression.Property(parameter, property);
        var orderbeExpression = Expression.Lambda(memberExpression, new ParameterExpression[] { parameter });

        var orderMethod = isAsc ? "OrderBy" : "OrderByDescending";
        var resultExpression = Expression.Call(typeof(Queryable), orderMethod, new Type[] { queryable.ElementType, property.PropertyType },
                                               new Expression[] { queryable.Expression, Expression.Quote(orderbeExpression) });

        return queryable.Provider.CreateQuery<T>(resultExpression);
    }
}

动态查询指定属性

通过传递一个字符串,然后查询指定的列返回

var userQueryable = UserDto.GetUserDtos().AsQueryable();

ParameterExpression parameter1 = Expression.Parameter(typeof(UserDto));
MemberExpression men = Expression.Property(parameter1, "Name");
var selectFieldExpression = Expression.Lambda<Func<UserDto, string>>(men, new ParameterExpression[] { parameter1 });
var result1 = userQueryable.Select(selectFieldExpression).ToList();

通过编写一个映射后的类实现简单的对应映射转换

public void DynamicSelect()
{
    var userQueryable = UserDto.GetUserDtos().AsQueryable();
    // 简单映射转换
    var result2 = userQueryable.SelectMapper<UserDto, UserTest>().ToList();
    // 执行SQL:SELECT u.id AS "Id", u.account AS "Account" FROM test."user" AS u
}

public static class ExpressExtensons
{
    /// <summary>
    /// 查询映射
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <typeparam name="M"></typeparam>
    /// <param name="queryable"></param>
    /// <returns></returns>
    public static IQueryable<M> SelectMapper<T, M>(this IQueryable<T> queryable)
    {
        var parameter = Expression.Parameter(typeof(T), "t");
        var newExpression = Expression.New(typeof(M));

        var mapperType = typeof(T).GetProperties();

        var listBinding = new List<MemberBinding>();
        foreach (var item in typeof(M).GetProperties())
        {
            if (!mapperType.Any(t => t.Name == item.Name))
            {
                continue;
            }

            var mem = Expression.Property(parameter, item.Name);// t.name
            var member = typeof(M).GetMember(item.Name)[0];
            MemberBinding memBinding = Expression.Bind(member, mem);// 这里传mem是用t.name给他赋值
            listBinding.Add(memBinding);
        }

        var memberExp = Expression.MemberInit(newExpression, listBinding);
        var selectExpression = Expression.Lambda<Func<T, M>>(memberExp, new ParameterExpression[] { parameter });
        return queryable.Select(selectExpression);
    }
}

资料

表达式树资料:https://www.cnblogs.com/li-peng/p/3154381.html