C#表达式树Expression动态创建表达式

上一篇中说到了 Expression 的一些概念性东西,其实也是为了这一篇做知识准备。为了实现 EFCore 的多条件、连表查询,简化查询代码编写,也就有了这篇文章。

在一些管理后台中,对数据进行多条件查询是一件很普遍的事情,比如在用户列表需要实现可以对 "用户名"、"手机号"、"账户是否冻结" 等等一系列的条件查询,常见的处理方式就是通过一系列 if...else... 来对条件进行拼接。这会导致查询接口实现起来堆叠了一堆看起来有用但实际很繁琐的代码。所以根据前后端请求报文协商,我们就可以按照一定的格式来动态创建表达式树。

创建 QueryEntity 类

QueryEntity 是前端向 API 传递的参数列表,通过这个类,服务端可以知道前端需要查询哪个字段,使用什么方法(Equals、Contains)过滤。

/// <summary>
/// 查询实体
/// </summary>
public class QueryEntity
{
    /// <summary>
    /// 字段名称
    /// </summary>
    public string Key { get; set; }

    /// <summary>
    /// 值
    /// </summary>
    public string Value { get; set; }

    /// <summary>
    /// 操作方法,对应OperatorEnum枚举类
    /// </summary>
    public string Operator { get; set; }

    /// <summary>
    /// 逻辑运算符,只支持AND、OR
    /// </summary>
    public string LogicalOperator { get; set; }
}

创建 OperatorEnum 类

OperatorEnum 这是一个操作方法的枚举类,规定了 API 允许的查询方法,比如 Equals、Contains 等等。

/// <summary>
/// 操作方法枚举
/// </summary>
public enum OperatorEnum
{
    /// <summary>
    /// 等于
    /// </summary>
    Equals,

    /// <summary>
    /// 不等于
    /// </summary>
    NotEqual,

    /// <summary>
    /// 包含
    /// </summary>
    Contains,

    /// <summary>
    /// 由什么开始
    /// </summary>
    StartsWith,

    /// <summary>
    /// 由什么结束
    /// </summary>
    EndsWith,

    /// <summary>
    /// 大于
    /// </summary>
    Greater,

    /// <summary>
    /// 大于等于
    /// </summary>
    GreaterEqual,

    /// <summary>
    /// 小于
    /// </summary>
    Less,

    /// <summary>
    /// 小于等于
    /// </summary>
    LessEqual,
}

创建 ExpressionExtension 类

ExpressionExtension 类实现了表达式树的动态创建,将前端传入的多条件查询转换成表达式,用于 EFCore 的查询。

/// <summary>
/// 表达式扩展
/// </summary>
/// <typeparam name="T">泛型</typeparam>
public static class ExpressionExtension<T> where T : class, new()
{
    /// <summary>
    /// 表达式动态拼接
    /// </summary>
    public static Expression<Func<T, bool>> ExpressionSplice(List<QueryEntity> entities)
    {
        if (entities.Count < 1)
        {
            return ex => true;
        }
        var expression_first = CreateExpressionDelegate(entities[0]);
        foreach (var entity in entities.Skip(1))
        {
            var expression = CreateExpressionDelegate(entity);
            InvocationExpression invocation = Expression.Invoke(expression_first, expression.Parameters.Cast<Expression>());
            BinaryExpression binary;
            // 逻辑运算符判断
            if (entity.LogicalOperator.ToUpper().Equals("OR"))
            {
                binary = Expression.Or(expression.Body, invocation);
            }
            else
            {
                binary = Expression.And(expression.Body, invocation);
            }
            expression_first = Expression.Lambda<Func<T, bool>>(binary, expression.Parameters);
        }
        return expression_first;
    }

    /// <summary>
    /// 创建 Expression<TDelegate>
    /// </summary>
    private static Expression<Func<T, bool>> CreateExpressionDelegate(QueryEntity entity)
    {
        ParameterExpression param = Expression.Parameter(typeof(T));

        Expression key = param;
        var entityKey = entity.Key.Trim();
        // 包含'.',说明是父表的字段
        if (entityKey.Contains('.'))
        {
            var tableNameAndField = entityKey.Split('.');
            key = Expression.Property(key, tableNameAndField[0].ToString());
            key = Expression.Property(key, tableNameAndField[1].ToString());
        }
        else
        {
            key = Expression.Property(key, entityKey);
        }

        Expression value = Expression.Constant(ParseType(entity));
        Expression body = CreateExpression(key, value, entity.Operator);
        var lambda = Expression.Lambda<Func<T, bool>>(body, param);
        return lambda;
    }

    /// <summary>
    /// 属性类型转换
    /// </summary>
    /// <param name="entity">查询实体</param>
    /// <returns></returns>
    private static object ParseType(QueryEntity entity)
    {
            try
            {
                PropertyInfo property;
                // 包含'.',说明是子类的字段
                if (entity.Key.Contains('.'))
                {
                    var tableNameAndField = entity.Key.Split('.');

                    property = typeof(T).GetProperty(tableNameAndField[0], BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance);
                    property = property.PropertyType.GetProperty(tableNameAndField[1], BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance);
                }
                else
                {
                    property = typeof(T).GetProperty(entity.Key, BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance);
                }

                return Convert.ChangeType(entity.Value, property.PropertyType);
            }
            catch (Exception)
            {
                throw new ArgumentException("字段类型转换失败:字段名错误或值类型不正确");
            }
    }

    /// <summary>
    /// 创建 Expression
    /// </summary>
    private static Expression CreateExpression(Expression left, Expression value, string entityOperator)
    {
        if (!Enum.TryParse(entityOperator, true, out OperatorEnum operatorEnum))
        {
            throw new ArgumentException("操作方法不存在,请检查operator的值");
        }

        return operatorEnum switch
        {
                OperatorEnum.Equals => Expression.Equal(left, Expression.Convert(value, left.Type)),
                OperatorEnum.NotEqual => Expression.NotEqual(left, Expression.Convert(value, left.Type)),
                OperatorEnum.Contains => Expression.Call(left, typeof(string).GetMethod("Contains", new Type[] { typeof(string) }), value),
                OperatorEnum.StartsWith => Expression.Call(left, typeof(string).GetMethod("StartsWith", new Type[] { typeof(string) }), value),
                OperatorEnum.EndsWith => Expression.Call(left, typeof(string).GetMethod("EndsWith", new Type[] { typeof(string) }), value),
                OperatorEnum.Greater => Expression.GreaterThan(left, Expression.Convert(value, left.Type)),
                OperatorEnum.GreaterEqual => Expression.GreaterThanOrEqual(left, Expression.Convert(value, left.Type)),
                OperatorEnum.Less => Expression.LessThan(left, Expression.Convert(value, left.Type)),
                OperatorEnum.LessEqual => Expression.LessThanOrEqual(left, Expression.Convert(value, left.Type)),
                _ => Expression.Equal(left, Expression.Convert(value, left.Type)),
        };
    }
}

使用示例

例如有以下两个实体类,Address 是 User 的子类

public class User
{
    public int Id { get; set; }

    public string Name { get; set; } = string.Empty;

    public int Age { get; set; }

    public DateTime CreateTime { get; set; }

    public Address Address { get; set; }

}

public class Address
{
    public string Province { get; set; }

    public string City { get; set; }
}

单条件查询

查询用户表中名称(name) 包含 "chen" :

List<QueryEntity> list = new List<QueryEntity>
{
    new QueryEntity
    {
        Key = "name",
        Value = "chen",
        Operator = "Contains"
    }
};
var expression = ExpressionExtension<User>.ExpressionSplice(list);
// expression = Param_0 => Param_0.Name.Contains("chen")

查询用户表中年龄(age) 大于等于 18:

List<QueryEntity> list = new List<QueryEntity>
{
    new QueryEntity
    {
        Key = "age",
        Value = "18",
        Operator = "GreaterEqual"
    }
};
var expression = ExpressionExtension<User>.ExpressionSplice(list);
// expression = Param_0 => Param_0.Name.GreaterThanOrEqual(18)

多条件查询

查询用户表中名称(name) 包含 "chen" 并且年龄(age) 大于等于 18:

List<QueryEntity> list = new List<QueryEntity>
{
    new QueryEntity
    {
        Key = "name",
        Value = "chen",
        Operator = "Contains"
    },
    new QueryEntity
    {
        Key = "age",
        Value = "18",
        Operator = "GreaterEqual",
        // 注意:这里得填入 "AND",代表两个条件是并且的关系,如果需要查询名称包含 "chen" 或者 年龄大于等于18,则填入 "OR"
        "logicalOperator": "AND"
    }
};
var expression = ExpressionExtension<User>.ExpressionSplice(list);
// expression = Param_0 => ((Param_0.Status >= Convert(1, Int32)) And Invoke(Param_1 => Param_1.OpenId.Contains("9JJdFTVt6oimCgdbW61sk"), Param_0))

多表查询

查询用户表中名称(name) 包含 "chen" 并且 地址(address)在广东省:

List<QueryEntity> list = new List<QueryEntity>
{
    new QueryEntity
    {
        Key = "name",
        Value = "chen",
        Operator = "Contains"
    },
    new QueryEntity
    {
        Key = "address.Province",
        Value = "广东省",
        Operator = "Equals",
        // 注意:这里得填入 "AND",代表两个条件是并且的关系,如果需要查询名称包含 "chen" 或者 年龄大于等于18,则填入 "OR"
        "logicalOperator": "AND"
    }
};

var expression = ExpressionExtension<BookingRecord>.ExpressionSplice(list);
// expression = {Param_0 => ((Param_0.Address.Province == Convert("广东省", String)) And Invoke(Param_1 => Param_1.Name.Contains("chen"), Param_0))}

到此这篇关于C#表达式树Expression动态创建表达式的文章就介绍到这了。希望对大家的学习有所帮助,也希望大家多多支持我们。

(0)

相关推荐

  • C#表达式树Expression基础讲解

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

  • C# 表达式目录树Expression的实现

    目录 表达式目录树 表达式目录树的拼装 应用 Linq to SQL ExpressionVisitor 表达式目录扩展 通过表达式目录树实现 表达式目录树 表达式目录树:语法树,或者说是一种数据结构 1.表达式目录树Expression:System.Linq.Expressions; 2.描述了多个变量或者和常量之间的关系,按照一定的规则进行组装! 可以向委托一样使用lambd表达式快捷声明: 不能有语句体,声明只能有一行代码: 可以通过Compile(),编译成一个委托: Func<int

  • 浅谈c#表达式树Expression简单类型比较demo

    实例如下: using System; using System.Linq.Expressions; class DynamicPredicate { public static Expression<Func<T, T, bool>> Generate<T>(string op) { ParameterExpression x = Expression.Parameter(typeof(T), "x"); ParameterExpression y

  • C# 表达式树Expression Trees的知识梳理

    目录 简介 Lambda 表达式创建表达式树 API 创建表达式树 解析表达式树 表达式树的永久性 编译表达式树 执行表达式树 修改表达式树 调试 简介 表达式树以树形数据结构表示代码,其中每一个节点都是一种表达式,比如方法调用和 x < y 这样的二元运算等. 你可以对表达式树中的代码进行编辑和运算.这样能够动态修改可执行代码.在不同数据库中执行 LINQ 查询以及创建动态查询. 表达式树还能用于动态语言运行时 (DLR) 以提供动态语言和 .NET Framework 之间的互操作性. 一.

  • C#简单实现表达式目录树(Expression)

    1.什么是表达式目录树 :简单的说是一种语法树,或者说是一种数据结构(Expression) 2.用Lambda声明表达式目录树: Expression<Func<int, int, int>> exp = (n, m) => n * m + 2; //表达试目录树的方法体只能是一行,不能有大括号.比如: //Expression<Func<int, int, int>> exp1 = (m, n) => // { // return m * n

  • 带你一文了解C#中的Expression

    我们书接上文,我们在了解LINQ下面有说到在本地查询IEnumerbale主要是用委托来作为传参,而解析型查询 IQueryable则用Expression来作为传参: public static IEnumerable<T> Where<T>(this IEnumerable<T> enumable, Func<T, bool> func) public static IQueryable<T> Where<T>(this IQue

  • C#之Expression表达式树实例

    本文实例讲述了C#之Expression表达式树,分享给大家供大家参考.具体实现方法如下: 表达式树表示树状数据结构的代码,树状结构中的每个节点都是一个表达式,例如一个方法调用或类似 x < y 的二元运算 1.利用 Lambda 表达式创建表达式树 复制代码 代码如下: Expression<Func<int, int, int, int>> expr = (x, y, z) => (x + y) / z; 2.编译表达式树,该方法将表达式树表示的代码编译成一个可执行

  • C#表达式树Expression动态创建表达式

    上一篇中说到了 Expression 的一些概念性东西,其实也是为了这一篇做知识准备.为了实现 EFCore 的多条件.连表查询,简化查询代码编写,也就有了这篇文章. 在一些管理后台中,对数据进行多条件查询是一件很普遍的事情,比如在用户列表需要实现可以对 "用户名"."手机号"."账户是否冻结" 等等一系列的条件查询,常见的处理方式就是通过一系列 if...else... 来对条件进行拼接.这会导致查询接口实现起来堆叠了一堆看起来有用但实际很繁琐

  • C#用表达式树构建动态查询的方法

    前文介绍了C#中表达式树的基本知识,在实际中,表达式树有很多用法,这里举几个例子,说明如何使用表达式树构建动态查询,从而扩展LINQ的查询方法. 在LINQ中,只要数据源实现了IQuerable<T>接口,表达式树就可以用来表示结构化查询.比如,LINQ提供了用来查询关系数据源的IQueryable<T>接口的实现,C#编译器在执行这类数据源查询时,会在运行时生成表达式树,然后,查询会遍历表达式树的数据结构,然后将其转换成针对特定数据源的合适的查询语言. 下面的几个例子演示了如何使

  • C#使用表达式树(LambdaExpression)动态更新类的属性值(示例代码)

    有看过我之前发表过的C#相关文章分享和阅读过我代码的朋友们可能会在我的代码里面经常看到各种各样的λ表达式动态拼接,C#的λ表达式树是一个好东西,也是别的语言学不来的,熟悉掌握λ表达式就能够实现各种场景的个性化操作,如动态拼接查询条件.排序方式等,也能够实现替代反射的高性能操作,比如我们常用到的IQueryable和IEnumerable,每个扩展方法就全是λ表达式树. 本文给大家分享C#使用表达式树(LambdaExpression)动态更新类的属性值的相关知识,在某些业务中会遇到需要同步两个类

  • C#表达式树的基本用法讲解

    表达式树使用一种类似树的结构来表示代码,它的每个节点都是一个表达式,比如方法调用和x<y这样的二元运算等.我们可以对表达式树的内容进行编辑和运算,这样能够动态修改可执行代码,以及动态创建查询等.我们可以使用匿名lambda表达式或者C# API来创建表达式树. 这一系列文章,主要是对C#表达式树的一种总结,基本知识参考MSDN的内容 这部分内容可以直接到MSDN上查看,后面的几篇文章主要分享一下,在工作中碰到的应用到表达式树的部分,谨做为记录和分享. 生成表达式树 通过lambda表达式创建表达

  • ASP.NET Core中如何使用表达式树创建URL详解

    表达式树(Expression Tree) 表达式树是不可执行的代码,它只是用于表示一种树状的数据结构,树上的每一个节点都表示为某种表达式类型,大概有25种表达式类型,它们都派生自Expression类.创建表达式树具体有两个优势: 1.对表达式树的代码进行编辑修改,使表达式树中的代码变成动态代码,根据不同的数据库修改树上的代码逻辑从而达到动态切换数据库查询语句的目的,用表达式树可以动态构建针对不同数据库的查询语句. 2.完成类似反射访问未知对象的属性,通过动态构造表达式树,生成委托. 当我们在

  • C#表达式树讲解

    表达式树的概念 表达式树的创建有 Lambda法 和 组装法. 学习表达式树需要 委托.Lambda.Func<> 基础. 表达式树 形状可以参考二叉树. 可以把表达式树理解成 数学表达式. 数学表达式的所有常量.符号为表达式树的底节点.每一次计算生成的结果是一个结点,或者说他们的共同结点就是他们应该进行的运算. 生成表达式树 表达式树的创建有 Lambda表达式法 和 组装法 为了方便,这里指定生成的表达式为 ( i * j ) + ( x * y ) 他们的运算是这样的 Lambda 生成

随机推荐