EF Core 表达式树、结构、动态构造

EF Core 表达式树、结构、动态构造

EF Core 表达式树

1.表达式树(Expression Tree):树形数据结构表示代码以表示逻辑运算,以便可以在运行时访问逻辑运算的结构。

2.Expression <TDelegate>类型

3.从Lambda表达式来生成表达式树:

 Expression<Func<Book,bool>> expression = b=>.Price > 5;
 Console.WriteLine(expression);//e => (e.Price > 5) 打印结果
 //把Expression<Func<Book,bool>>换成Func<Book,bool>如下
     
 Func<Book,bool> func = b=>.Price > 5;
 Console.WriteLine(func);
 //打印结果System.Func`2[Book,System.Boolean]
 
 
查看生成的SQL
 -- Expression<Func<Book,bool>> expression = b=>.Price > 5;    
      SELECT t."Id", t."AuthorName", t."Price", t."PubTime", t."Title"
       FROM "T_Books" AS t
       WHERE t."Price" > 5.0
       
 --Func<Book,bool> func = b=>.Price > 5;
       SELECT t."Id", t."AuthorName", t."Price", t."PubTime", t."Title"
       FROM "T_Books" AS t
       
总结:

Expression表达树生成的sql本身自带逻辑运算,在数据库就已经筛选了数据(对应之前---就是服务端评估)

普通委托是把数据全部加载到内存然后再筛选的,如果数据量大,这种处理不合适(对应之前-是客户端评估)

Expression对象存储了运算逻辑,他把运算逻辑保存成抽象语法树(AST),可以在运行时动态获取运算逻辑。而普通委托则没有

详情可以调试的时候看expression的body的left和right节点(如何生成的需要了解编译原理,有兴趣的可以自己查阅相关资料)

 

如何查看表达式树的结构

1.VS中调试程序,然后用【快速监视】的方式查看变量expression的值,展开Raw View.

 Expression<Func<Book, bool>> e = b => b.Price > 5 || b.AuthorName.Contains("杨");

2.整个表达式树是一个“或”(OrElse)类型的节点,左节点(Left)是b.Price>5表达式,右节点(Right)是b.Authorname.Contains(“杨”)表达式。而b.Price>5这个表达式又是一个“大于”(GreaterThan)类型的节点,左节点(Left)是b.Price,右节点(Right)是5.

3.AST:抽象语法树

(1):zspitz开发的ExpressionTreeVisualizer

https://github.com/zspitz/ExpressionTreeVisualizer

安装VS插件(由于这个插件对版本要求很严格,所以不推荐使用)

(2):主要推荐使用代码查看结构,简单方便

安装ExpressionTreeToString Nuget包

 //使用方式如下
 Expression<Func<Book, bool>> e = b => b.AuthorName.Contains("杨") || b.Price > 30;
 Console.WriteLine(e.ToString("Object notation", "C#"));
生成表达式树的结构代码
 var b = new ParameterExpression {//参数
     Type = typeof(Book),//Book类型
     IsByRef = false,
     Name = "b"   //参数名字为b
 };
 
 new Expression<Func<Book, bool>> {
     NodeType = ExpressionType.Lambda,//根节点是一个Lambda
     Type = typeof(Func<Book, bool>),
     Parameters = new ReadOnlyCollection<ParameterExpression> {
         b
    },//整个Lambda表达式有一个参数 b
     Body = new BinaryExpression {//Lambda的体(Body)是一个二元表达式
         NodeType = ExpressionType.GreaterThan,//二元表达式的节点类型是 大于 >
         Type = typeof(bool),
         Left = new MemberExpression {//左节点是 访问成员表达式
             Type = typeof(double),
             Expression = b,
             Member = typeof(Book).GetProperty("Price")
        },
         Right = new ConstantExpression {//右节点是 常量表达式
             Type = typeof(double),//double类型
             Value = 5 //值为5 我们写代码的时候 double v= 5;编译器帮我们转化了
        }
    },
     ReturnType = typeof(bool)
 }

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

根据上面打印出来的代码仿写一个

 //动态创建和Expression<Func<Book, bool>> e = b =>b.Price > 5一样的代码
 
 ParameterExpression paramB = Expression.Parameter(typeof(Book), "b");//参数类型与名字
 MemberExpression exprLeft = Expression.MakeMemberAccess(paramB, typeof(Book).GetProperty("Price"));//成员访问表达式左节点
 //右节点是 常量表达式
 ConstantExpression exprRight = Expression.Constant(5.0, typeof(double));
 //二元表达式的节点类型是 大于 >
 BinaryExpression exprBody = Expression.MakeBinary(ExpressionType.GreaterThan, exprLeft, exprRight);
 Expression<Func<Book, bool>> expr1 = Expression.Lambda<Func<Book, bool>>(exprBody, paramB);
 ctx.Books.Where(expr1).ToList();
 Console.WriteLine(expr1.ToString("Object notation", "C#"));

小提示:using static xxx(可以直接调用该方法)

使用Factory methods 生成的表达式树是以工厂模式生成的 比上述原生要简单许多
 Expression<Func<Book, bool>> e = b => b.Price > 5;
 Console.WriteLine(e.ToString("Factory methods", "C#"));
工厂模式生成
 var b = Parameter(
     typeof(Book),
     "b"
 );
 
 Lambda(
     GreaterThan(
         MakeMemberAccess(b,
             typeof(Book).GetProperty("Price")
        ),
         Constant(5)
    ),
     b
 )
使用工厂模式构造表达式树:用此代码打印出来的与上述一样
 var b = Parameter(typeof(Book), "b");
 var expr1 = Lambda<Func<Book, bool>>(GreaterThan(MakeMemberAccess(b, typeof(Book).GetProperty("Price")),
  Constant(5.0)), b);
 using TestDbContext ctx = new TestDbContext();
 ctx.Books.Where(expr1).ToList();
 Console.WriteLine(expr1.ToString("Factory methods", "C#"));

动态构建表达式树

 using System.Linq.Expressions;
 using System.Reflection;
 using static System.Linq.Expressions.Expression;
 var q1 = QueryBooks("Price", 65.0).ToList();
 foreach (var item in q1)
 {
  Console.WriteLine("标题:"+item.Title+",发布时间:"+item.PubTime+",作者:"+item.AuthorName);
 }
 var q2 = QueryBooks("AuthorName", "first64").ToList();
 foreach (var item in q2)
 {
  Console.WriteLine("标题:" + item.Title + ",发布时间:" + item.PubTime + ",作者:" + item.AuthorName);
 }
 var q3 = QueryBooks("Title", "零基础趣学C语言").ToList();
 foreach (var item in q3)
 {
  Console.WriteLine("标题:" + item.Title + ",发布时间:" + item.PubTime + ",作者:" + item.AuthorName);
 }
 
 IEnumerable<Book> QueryBooks(string propName, object value)
 {
     Type type = typeof(Book);
     PropertyInfo propInfo = type.GetProperty(propName);
     Type propType = propInfo.PropertyType;
     var b = Parameter(typeof(Book), "b");
     Expression<Func<Book, bool>> expr;
     if (propType.IsPrimitive)//如果是int、double等基本数据类型
    {
         expr = Lambda<Func<Book, bool>>(Equal(
                 MakeMemberAccess(b, typeof(Book).GetProperty(propName)),
                 Constant(value)), b);
    }
     else//如果是string等类型
    {
         expr = Lambda<Func<Book, bool>>(MakeBinary(ExpressionType.Equal,
                 MakeMemberAccess(b, typeof(Book).GetProperty(propName)),
                 Constant(value), false, propType.GetMethod("op_Equality")
            ), b);
    }
     TestDbContext ctx = new TestDbContext();
     return ctx.Books.Where(expr).ToArray();
 }

尽量避免使用动态构建表达式树

 //尽量避免使用动态构建表达式树
 
 
 var q2 = QueryBooks("net core 学习64",null,66,1).ToList();
 foreach (var item in q2)
 {
     Console.WriteLine("标题:" + item.Title + ",发布时间:" + item.PubTime + ",作者:" + item.AuthorName);
 }
 
 Book[] QueryBooks(string title, double? lowerPrice, double? upperPrice, int orderByType)
 {
     using TestDbContext ctx = new TestDbContext();
     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();
 }
总结:

1.动态构建表达式树的代码很复杂,而且易读性差,维护起来不方便

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

第三方Nuget包 支持使用字符串拼接进行数据操作

System.Linq.Dynamic.Core 具体看官方文档

地址:https://github.com/zzzprojects/System.Linq.Dynamic.Core/blob/master/README.md

 using System.Linq.Dynamic.Core;
 
 using var ctx = new TestDbContext();
 ctx.Books.Where($"Price > 0 and Price < 66").Select("new (Title,AuthorName)").ToDynamicArray();

生成的对应的SQL

      SELECT t."Title", t."AuthorName"
       FROM "T_Books" AS t
       WHERE (t."Price" > 0.0) AND (t."Price" < 66.0)
 
posted @ 2022-07-31 00:40  忽如一夜娇妹来  阅读(705)  评论(0编辑  收藏  举报