EntityFrameworkCore ORM - Expression动态表达式树

表达式树以树形数据结构表示代码,其中每一个节点都是一种表达式,比如方法调用和 x < y 这样的二元运算等。可以对表达式树中的代码进行编辑和运算。 这样能够动态修改可执行代码、在不同数据库中执行 LINQ 查询以及创建动态查询。 表达式树还能用于动态语言运行时 (DLR) 以提供动态语言和 .NET 之间的互操作性,同时保证编译器编写员能够发射表达式树而非 Microsoft 中间语言 (MSIL)。

一、基础用法

(1) Expression 和 Func 的区别

  • Expression 存储了运算逻辑,可以将其保存成抽象语法树(AST),可以在运行时动态获取运算逻辑。
  • Func 只是存储了结果,无法保存成语法树,也无法动态获取运算逻辑。

所以,在 EFCore 中,使用表达式对数据库数据进行查询中,我们应该选择 Expression 而不是 Func,因为使用了 Func ,实际上并无法将 Func 中的表达式转换成 SQL,而是在将所有数据加载到内存后,在内存中在过滤 Func 中的条件。

简单来说就是,此时要筛选 User 表中年龄大于18的数据,可以有这两种写法

// 这种写法,实际生成的 SQL 语句, 大概是这样的 SELECT * FROM User as T WHERE T.age > 18
Expression<Func<User,bool>> expression1 = x => x.Age > 18;
dbContext.User.Where(expression1).toList();

// 而这种, 生成的语句是这样的 SELECT * FROM User, 然后将 User 表中所有数据加载到内存中后, 在进行 age > 18 的过滤
Func<User, bool> func1 = x => x.Age > 18;
dbContext.User.Where(func1).toList();

(2) 通过代码创建表达式树

  • ParameterExpression
  • BinaryExpression
  • MethodCallExpression
  • ConstantExpression
  • UnaryExpression

这些类几乎都没有提供构造方法,而且所有的属性都几乎只是只读。因此我们一般不会直接创建这些类的实例,而是调用 Expression 类的 Parameter、MakeBinary、Call、Constant等静态方法来生成,这些静态方法我们一般称作创建表达式树的工厂方法,而属性则通过方法参数类设置。

动态将表达式:u => u.Age >= 18; 通过代码构建出来

一般构建步骤:

  • 先创建 ParameterExpression
  • 接着由里到外逐步构建
  • 先左节点(Left)
  • 后右节点(Right)
  • 接着Body节点
  • 将其拼接成 Expression
public IActionResult GetUserByManualExpression()
{
    ParameterExpression parameterExpression = Expression.Parameter(type:typeof(User), name: "u");
    ConstantExpression right = Expression.Constant(18);
    MemberExpression left = Expression.MakeMemberAccess(parameterExpression, member: typeof(User).GetProperty("Age"));
    BinaryExpression body = Expression.GreaterThanOrEqual(left, right);

    Expression<Func<User, bool>> expression = Expression.Lambda<Func<User, bool>>(body, parameters: parameterExpression);

    var data = _userService.GetUsers(expression);

    return Ok(new
    {
        code = 200,
        msg = "OK",
        data
    });
}

二、动态创建表达式

从上述Expression表达式树元素中,可以拆解出如下几个对象定义:字段名、字段值、值类型以及逻辑运算符共四个部分。

public class Conditions
{
    /// <summary>
    /// 字段名称
    /// </summary>
    public string Key { get; set; }
    /// <summary>
    /// 值(不能用string,对所有传入的值类型才可智能转换处理)
    /// </summary>
    public object Value { get; set; }
    /// <summary>
    /// 值类型
    /// </summary>
    public string ValueType { get; set; }
    /// <summary>
    /// 逻辑运算符
    /// </summary>
    public OperatorEnum Operator { get; set; }
}

Public Enum OperatorEnum
    //包含
    Contains
    //MethodCallExpression带方法
    [Call]
    //等于
    Equal
    //大于
    Greater
    //大于等于
    GreaterEqual
    //小于
    Less
    //小于等于
    LessEqual
    //不等于
    NotEqual
    //等于多个in (a,b,c)
    [In]
    //范围内 >= a and <= b
    Between

End Enum

首先我们要创建一个查询条件转化为表达式的泛型功能类 如 ExpressionParser<T> 至于为什么要用泛型类目的很明确就是为了适配不同的模型参数。转化条件为表达式 那么处理一个方法来接受条件 返回表达式,条件可以按照自己的模式去设置。

public class ExpressionParser<T>
{
    private var parameter = Expression.Parameter(typeof(T));
    public Expression<Func<T, bool>> ParserConditions(IEnumerable<Conditions> conditions)
    {
        // 将条件转化成表达是的Body
        var query = ParseExpressionBody(conditions);
        return Expression.Lambda<Func<T, bool>>(query, parameter);
    }
}

(1) 条件转表达式具体处理

具体去实现  ParseExpressionBody 条件 枚举提供操作方式 如:(like 、 = 、!= 、>   、<  、>=  、<=  、in 、 between)

    private Expression ParseExpressionBody(IEnumerable<Conditions> conditions)
    {
        if (conditions == null || conditions.Count() == 0)
        {
            return Expression.Constant(true, typeof(bool));
        }
        else if (conditions.Count() == 1)
        {
            return ParseCondition(conditions.First());
        }
        else
        {
            Expression left = ParseCondition(conditions.First());
            Expression right = ParseExpressionBody(conditions.Skip(1));
            return Expression.AndAlso(left, right);
        }
    }

    private Expression ParseCondition(Conditions condition)
    {
        ParameterExpression p = parameter;
        Expression key = Expression.Property(p, condition.Key);
        Expression value = Expression.Constant(condition.Value);
        switch (condition.Operator)
        {
            case OperatorEnum.Contains:
                return Expression.Call(key, typeof(string).GetMethod("Contains",new Type[] { typeof(string) }), value);
            case OperatorEnum.Equal:
                return Expression.Equal(key, Expression.Convert(value, key.Type));
            case OperatorEnum.Greater:
                return Expression.GreaterThan(key, Expression.Convert(value, key.Type));
            case OperatorEnum.GreaterEqual:
                return Expression.GreaterThanOrEqual(key, Expression.Convert(value, key.Type));
            case OperatorEnum.Less:
                return Expression.LessThan(key, Expression.Convert(value, key.Type));
            case OperatorEnum.LessEqual:
                return Expression.LessThanOrEqual(key, Expression.Convert(value, key.Type));
            case OperatorEnum.NotEqual:
                return Expression.NotEqual(key, Expression.Convert(value, key.Type));
            case OperatorEnum.In:
                return ParaseIn(p, condition);
            case OperatorEnum.Between:
                return ParaseBetween(p, condition);
            default:
                throw new NotImplementedException("不支持此操作");
        }
    }

(2) In和between的处理

    private Expression ParaseBetween(ParameterExpression parameter, Conditions conditions)
    {
        ParameterExpression p = parameter;
        Expression key = Expression.Property(p, conditions.Key);
        var valueArr = conditions.Value.Split(',');
        if (valueArr.Length != 2)
        {
            throw new NotImplementedException("ParaseBetween参数错误");
        }
        try
        {
            int.Parse(valueArr[0]);
            int.Parse(valueArr[1]);
        }
        catch {
            throw new NotImplementedException("ParaseBetween参数只能为数字");
        }
        Expression expression = Expression.Constant(true, typeof(bool));
        //开始位置
        Expression startvalue = Expression.Constant(int.Parse(valueArr[0]));
        Expression start = Expression.GreaterThanOrEqual(key, Expression.Convert(startvalue, key.Type));

        Expression endvalue = Expression.Constant(int.Parse(valueArr[1]));
        Expression end = Expression.GreaterThanOrEqual(key, Expression.Convert(endvalue, key.Type));
        return Expression.AndAlso(start, end);
    }

    private Expression ParaseIn(ParameterExpression parameter, Conditions conditions)
    {
        ParameterExpression p = parameter;
        Expression key = Expression.Property(p, conditions.Key);
        var valueArr = conditions.Value.Split(',');
        Expression expression = Expression.Constant(true, typeof(bool));
        foreach (var itemVal in valueArr)
        {
            Expression value = Expression.Constant(itemVal);
            Expression right = Expression.Equal(key, Expression.Convert(value, key.Type));
                            expression = Expression.Or(expression, right);
        }
        return expression;
    }

(3) 扩展分页、排序、查询条件

public static class ExpressionExtensions
{
    //扩展查询
    public static IQueryable<T> QueryConditions<T>(this IQueryable<T> query, IEnumerable<Conditions> conditions)
    {
        var parser = new ExpressionParser<T>();
        var filter = parser.ParserConditions(conditions);
        return query.Where(filter);
    }

    //扩展多条件排序(改进)
    public static IQueryable<T> OrderConditions<T>(this IQueryable<T> query, IEnumerable<OrderConditions> conditions)
    {
        var type = typeof(T);
        var parameter = Expression.Parameter(type, "p");

        Expression<Func<T, object>> orderby;
        foreach (var orderinfo in conditions)
        {
            var property = type.GetProperty(orderinfo.Key);
            var propertySelector = Expression.Property(parameter, property);

            var propertyAccess = Expression.PropertyOrField(parameter, orderinfo.Key);
            switch (property.PropertyType)
            {
                case object _ when typeof(DateTime):
                        orderby = Expression.Lambda<Func<T, object>>(Expression.Call(Expression.Property(parameter, orderinfo.Key), typeof(DateTime).GetMethod("ToString", Type.EmptyTypes)), parameter);
                        break;

                default:
                        orderby = Expression.Lambda<Func<T, object>>(propertySelector, parameter);
                        break;
            }

            if (orderinfo.Order == OrderSequence.DESC)
                query = query.OrderByDescending(orderby);
            else
                query = query.OrderBy(orderby);
        }

        return query;
    }


    //扩展分页
    public static IQueryable<T> Pager<T>(this IQueryable<T> query, int pageindex, int pagesize,out int itemCount)
    {
        itemCount = query.Count();
        return query.Skip((pageindex - 1) * pagesize).Take(pagesize);
    }
}

public enum OrderSequence
{
    ASC,
    DESC
}

三、具体调用

private void GetDgvHeader()
{

    using (var db = new DbContext())
    {
        List<Conditions> queryConditions = new List<Conditions>();
        // 多条件查询
        if (CmbColor.SelectedIndex > 0)
        {
            Conditions color_filter = new Conditions()
            {
                Key = "Color",
                Operator = OperatorEnum.Equal,
                Value = CmbColor.SelectedValue,
                ValueType = "String"
            };

            queryConditions.Add(color_filter);
        }

        if (CmbIBKind.SelectedIndex > 0)
        {
            Conditions ibkind_filter = new Conditions()
            {
                Key = "IbKind",
                Operator = OperatorEnum.Equal,
                Value = CmbIBKind.SelectedValue,
                ValueType = "String"
            };

            queryConditions.Add(ibkind_filter);
        }

        if (CmbChipProcess.SelectedIndex > 0)
        {
            Conditions chip_filter = new Conditions()
            {
                Key = "ChipProcess",
                Operator = OperatorEnum.Equal,
                Value = CmbChipProcess.SelectedValue,
                ValueType = "String"
            };

            queryConditions.Add(chip_filter);
        }

        // 副条件-Bin号
        if (CmbBinno.SelectedIndex > 0)
        {
            Conditions bin_filter = new Conditions()
            {
                Key = "Binno",
                Operator = OperatorEnum.Equal,
                Value = CmbBinno.SelectedValue,
                ValueType = "String"
            };

            queryConditions.Add(bin_filter);
        }
        // 副条件-分档
        if (CmbSpec.SelectedIndex > 0)
        {
            Conditions spec_filter = new Conditions()
            {
                Key = "Spec",
                Operator = OperatorEnum.Equal,
                Value = CmbSpec.SelectedValue.ToString().Replace("nm", ""),
                ValueType = "Double"
            };

            queryConditions.Add(spec_filter);
        }

        // 多条件排序
        List<OrderConditions> orderConditions = new List<OrderConditions>()
        {
            new OrderConditions() { Key = "Pname", Order = OrderSequence.ASC },
            new OrderConditions() { Key = "Binno", Order = OrderSequence.ASC }
        };

        var dtHeader = db.WaferQualityRole.Where(x => x.IsExec).QueryCondition(queryConditions).OrderConditions(orderConditions).ToList().LinqToDataTable();
    }
}

至此,常用的逻辑运算表达式封装及调用已基础完整,有兴趣的朋友仍可以对数据类型多态性进行完善实现,感谢阅读。

[2024-3-26已完善]

解决非string数据类型的无法转换的处理。

  • 19
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值