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

EF Core 表达式树

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

2.Expression 类型

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节点(如何生成的需要了解编译原理,有兴趣的可以自己查阅相关资料)

Expression树结构

如何查看表达式树的结构

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)