P5-3 表达式树

表达式树


什么是表达式树

  • 1、表达式树(Expression Tree):树形数据结构表示代码,以表示逻辑运算,以便可以在运行时访问逻辑运算的结构。
  • 2、Expression类型
  • 3、从Lambda表达式来生成表达式树:Expression<Func<Book, bool>> e1 = b =>b.Price > 5;

Expression和Func的区别

  • 1、把Expression<Func<Book, bool>>换成Func<Book, bool>,
    1
    2
    3
    //写成下面的版本:
    Func<Book, bool> e = b => b.Price > 5;
    ctx.Books.Where(e).ToList();
  • 2、Expression对象储存了运算逻辑,它把运算逻辑保存成抽象语法树(AST),可以在运行时动态获取运算逻辑。而普通委托则没有。
    • Expression是把语句翻译成SQL语句去运行,而普通委托是先把数据都获取到内存中,然后在进行筛选
    • Expression不能写方法体
    • 问题

可视化查看表达式树

VS内置查看AST

  • 1、在VS中调试程序,然后在【快速监视】窗口查看变量e1的值,展开【Raw View】(原始视图)
    • 快速监视
  • 2、AST:抽象语法树。

ExpressionTreeVisualizer可视化

  • 1、zspitz开发的ExpressionTreeVisualizer https://github.com/zspitz/ExpressionTreeVisualizer
    安装VS插件。
  • 2、目前:如果项目的.NET Core版本是5,那么可视化查看就会报BadImageFormatException异常,只能把项目降级到.NET Core 3.1才可以。
    • 这个插件的安装比较麻烦

通过代码查看表达式树

  • 1、Install-Package ExpressionTreeToString
  • 2、Expression<Func<Book, bool>> e = b => b.AuthorName.Contains(“杨中科”)||b.Price>30;
    Console.WriteLine(e.ToString(“Object notation”, “C#”));
    • 代码查看树

通过代码动态构建表达式树

  • 1、只有通过代码动态构建表达式树才能更好的发挥表达式树的能力。
  • 2、ParameterExpression、BinaryExpression、MethodCallExpression、ConstantExpression等类几乎都没有提供构造方法,而且所有属性也几乎都是只读的
    • 因此我们一般不会直接创建这些类的实例,而是调用Expression类的Parameter、MakeBinary、Call、Constant等静态方法来生成,这些静态方法我们一般称作创建表达式树的工厂方法,而属性则通过方法参数类设置

目标

  • 生成和如下硬编码的C#代码一样的表达式树:

Expression<Func<Book, bool>> e = b =>b.Price > 5

  • 其他的工厂方法可以看书本P147-158页
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    //手动代码创建的表达式树
    ParameterExpression paramExprB = Expression.Parameter(typeof(Book), "b"); //参数节点
    ConstantExpression constExpr5 = Expression.Constant(5.0, typeof(double)); //创建对应5这个常量的节点
    MemberExpression memExprPrice = Expression.MakeMemberAccess(paramExprB,
    typeof(Book).GetProperty("Price")); //创建访问b的Price属性操作的节点
    BinaryExpression binExoGreatreThan = Expression.GreaterThan(memExprPrice, constExpr5);
    Expression<Func<Book, bool>> expr1 = Expression.Lambda<Func<Book, bool>>(binExoGreatreThan, paramExprB);

    using (MyDbContext ctx = new MyDbContext())
    {
    ctx.Books.Where(expr1).ToArray();
    }

对比


让动态构建表达式树更简单

  • 1、通过代码来动态构造表达式树要求开发者精通表达式树的结构,甚至还需要了解CLR底层的机制。不过可以用ExpressionTreeToString来简化动态构造表达式树的代码。
  • 2、可以用ExpressionTreeToString的ToString(“Factory methods”, “C#”)输出类似于工厂方法生成这个表达式树的代码。
    • 生成表达式树
  • 3、输出的所有代码都是对于工厂方法的调用,不过调用工厂方法的时候都省略了Expression类。手动添加Expression或者用using static
  • 4、可能需要微调生成的代码。
    • 1.加上<Func<Book,bool>
    • 2.把Constant(5)变成5.0,这个版本没有考虑类型转换的问题
    • 工厂方法1

让构建“动态”起来

动态创建表达式树

  • 1、动态构建表达式树最有价值的地方就是运行时根据条件的不同生成不同的表达式树。
    • 可以先观察一个静态的表达式树的结构是什么样的,然后就能够更好的来写一个动态的表达式树了
  • 2、编写:static IEnumerable QueryBooks(string propName,object value)
    其中propName为要查询的属性的名字,value为待比较的值。
  • 3、先编写
    Expression<Func<Book, bool>> expr1 = b => b.Price == 5;
    Expression<Func<Book, bool>> expr2 = b => b.Title == “零基础趣学C语言”;
    查看代码再写代码 e1.ToString(“Factory methods”, “C#”)
  • 4、判断是否基本数据类型:if(propType.IsPrimitive)
  • 5、测试调用:QueryBooks(“Price”, 18.0);QueryBooks(“AuthorName”, “杨中科”);

不用Emit实现Select的动态化

需求

  • 1、Select(b=>new{b.Id,b.Name})
  • 2、运行时动态设定Select查询出来的属性,需要使用Emit技术来采用动态生成IL的技术来在运行时创建一个类。难度大!
  • 3、简单的在运行时动态设定Select查询出来的属性方法。

实现

  • 1、Select参数中传递一个数组:Select(b=>new object[] { b.Id,b.Title})
  • 2、把列对应的属性的访问表达式放到一个Expression数组中,然后使用Expression.NewArrayInit构建一个代表数组的NewArrayExpression表达式对象,然后就可以用这个NewArrayExpression对象来供Select调用来执行了。

避免动态构建表达式树

  • 1、动态构建表达式树的代码仍然复杂,而且易读性差,维护起来是一个让人头疼的事情。
  • 2、一般只有在编写不特定于某个实体类的通用框架的时候,由于无法在编译器确定要操作的类名、属性等,所以才需要编写动态构建表达式树的代码
    • 否则为了提高代码的可读性和可维护性,要尽量避免动态构建表达式树。而是用IQueryable的延迟执行特性来动态构造。

例子

  • 编写如下一个方法Book[] QueryBooks(string title, double? lowerPrice, double? upperPrice, int orderByType),用于根据指定的参数条件来进行数据的复合查询:
    • title如果不为空,则进行标题的匹配;
    • 如果lowerPrice不为空,则还要匹配价格不小于lowerPrice的书;
    • 如果upperPrice不为空,则还要匹配价格不大于upperPrice的书;
    • orderByType为排序规则,取值为1代表按照价格降序排列,取值为2代表按照价格升序排列,取值为3代表按照发表日期降序排列,取值为4代表按照发表日期升序排列。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
//测试方法
foreach (var b in QueryBooks(null,3,200,1))
{
Console.WriteLine(b);
}
//根据指定的参数条件进行数据的查询
Book[] QueryBooks(string title,double? lowerPrice,double? upperPrice,int orderByType)
{
using(MyDbContext ctx =new MyDbContext())
{
IQueryable<Book> source = ctx.Books;
if (!string.IsNullOrEmpty(title))
{
source = source.Where(b => b.Title.Contains(title));

}
if (lowerPrice!=null)
{
source = source.Where(b => b.Price >= lowerPrice);
}
if (upperPrice !=null)
{
source = source.Where(b => b.Price <= upperPrice);
}
if (orderByType ==1)
{
source = source.OrderByDescending(b => b.Price);
}
else if (orderByType ==2)
{
source = source.OrderBy(b => b.Price);
}
return source.ToArray();
}
}

System.Linq.Dynamic.Core

  • 1、System.Linq.Dynamic.Core
  • 2、使用字符串格式的语法来进行数据操作
1
2
3
4
5
6
7

//拼接字符串创建动态表达式树
using MyDbContext ctx = new MyDbContext();
// ctx.Books.Where("Price>=3 and Price <= 60").Select("new(Title,Price)").ToDynamicArray();*/
//也可以差值字符串来实现
double price = 5;
ctx.Books.Where($"Price >={price} and Price <= 60 ").Select("new(Title,Price)").ToDynamicArray();

P5-3 表达式树
http://example.com/2024/09/28/Net Core2022教程/第5章:EF Core高级技术/P5-3 表达式树/
Author
John Doe
Posted on
September 28, 2024
Licensed under