欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

C#表达式树Expression基础讲解

程序员文章站 2022-03-02 16:12:25
什么是表达式树表达式树以树形数据结构表示代码,其中每一个节点都是一种表达式,比如方法调用和 x < y 这样的二元运算等。可以对表达式树中的代码进行编辑和运算。 这样能够动态修改可执行代码、在不...

什么是表达式树

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

在 c# 中,我们可以通过 expression 的方式来手动创建表达式树,比如:

[httpget]
public iactionresult expression()
{
    // 查询 年龄age 大于 18 的元素
    expression<func<user,bool>> expression1 = x => x.age > 18; 
    return ok();
}

那么,x.age > 18 这一表达式,它的树状结构是这样的:

C#表达式树Expression基础讲解

通过 visual studio 自带的查看变量或添加监视的方式,我们可以发现其中 树的根节点(nodetype)是 greaterthan,左节点(left)是 x.age,右节点(right)是 18。所以由此就可以大概画出树状结构。

C#表达式树Expression基础讲解

最后,通过这种树状结构,c# 就可以帮我们将表达式编译成具体的 sql 执行语句。

如果想更清晰的查看表达式树的结构,可以 nuget 一个包( expressiontreetostring ),将表达式结构转换成字符串

pm> install-package zspitz.util -version 0.1.116
expression<func<user, bool>> expression = u => u.age >= 18;
var treestr = expression.tostring("object notation", "c#");

// 输出为下面字符串
var u = new parameterexpression {
    type = typeof(user),
    isbyref = false,
    name = "u"
};

new expression<func<user, bool>> {
    nodetype = expressiontype.lambda,
    type = typeof(func<user, bool>),
    parameters = new readonlycollection<parameterexpression> {
        u
    },
    body = new binaryexpression {
        nodetype = expressiontype.greaterthanorequal,
        type = typeof(bool),
        left = new memberexpression {
            type = typeof(int),
            expression = u,
            member = typeof(user).getproperty("age")
        },
        right = new constantexpression {
            type = typeof(int),
            value = 18
        }
    },
    returntype = typeof(bool)
}

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();

通过代码创建表达式树

  • parameterexpression
  • binaryexpression
  • methodcallexpression
  • constantexpression

这些类几乎都没有提供构造方法,而且所有的属性都几乎只是只读。因此我们一般不会直接创建这些类的实例,而是调用 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
    });
}

到此这篇关于c#表达式树expression基础讲解的文章就介绍到这了。希望对大家的学习有所帮助,也希望大家多多支持。