我们平时封装的数据库查询方法Find
我们都把条件定死了,而不是动态的查询条件,不是不写,而是实现不了,写不出来,因为一个表的字段可能是Id,name等等不同的字段,而字段的值也可能是int,string,datetime等不同的数据类型,而操作符又可能是大于,小于,等于,包含这些,更有可能条件是一个条件,两个条件,三个条件等等,这些导致了我们没法写一个通用find封装,而只能根据需求写不同的方法。
上面对需求遇到的问题进行详细分析,我们真的没有办法解决上述问题吗,当然是可以的,可以用一个土办法,封装一个对象,这个对象就是一个条件的集合,包括字段,字段值,操作符这些,根据这个对象的集合就可以完整的构建一个sql语句的条件处理。这个方法不是这章我们要实现的办法,既然我们上一章介绍了表达式目录树,当然可通过表达式目录树来优雅的解决上述问题,表达式目录树既然是数据结构,我们就可以解析数据结构来按照自己的规则构造条件,又可以按照lambda的方式传入条件,这就不是我们常用的ORM框架模式吗。
对于表达式目录树的解析,官方是提供了ExpressionVisitor
类来做解析,那么是解析什么呢,我们来看一个实例:Expression
这个表达书目录树的解析就如下图的二叉树一样,一层一才能的剖析:
通过上图我们能清楚的看到,我们需要把这个表达式一层一层的解析,直到最后不能解析位置,解析报错变量,常量,操作符。下面我们就是演化一下ExpressionVisitor
是怎么解析的。
为了看得更清楚,我们继承ExpressionVisitor
把里面的部分方法重写一下,
///
/// 表达式目录树访问者扩展,遇到Add换成Subtract
///
public class OperationsVisitor : ExpressionVisitor
{
public Expression Modify(Expression expression)
{
return this.Visit(expression);
}
///
/// Visit是个入口,解读node表示式
/// 根据表达式的类型,将表达式调度到此类中更专用的访问方法之一的表达式
///
///
///
public override Expression Visit(Expression node)
{
Console.WriteLine($"Visit {node.ToString()}");
return base.Visit(node);
}
protected override Expression VisitBinary(BinaryExpression b)
{
Expression left = this.Visit(b.Left);
Expression right = this.Visit(b.Right);
if (b.NodeType == ExpressionType.Add)
{
return Expression.Subtract(left, right);
}
else if (b.NodeType == ExpressionType.Multiply)
{
//Expression left = this.Visit(b.Left);//可能还是需要进一步解析的
//Expression right = this.Visit(b.Right);
return Expression.Divide(left, right);
//return Expression.Divide(b.Left, b.Right);
}
Expression result = base.VisitBinary(b);//默认的二元访问,其实什么都不干
return result;
}
protected override Expression VisitConstant(ConstantExpression node)
{
return base.VisitConstant(node);
}
}
我们调用:
Expression<Func<int, int, int>> exp = (m, n) => m * n + 2;
OperationsVisitor visitor = new OperationsVisitor();
visitor.Visit(exp);
首先Visit
就是入口,开始解析表达式,表达式我们最初我们都是一个二元表达式,(m * n)+ 2,左边是 m * n 右边是2,
Visit会根据表达式类型自动调的相应的方法处理,第一层是二元表达式是所以会调用VisitBinary()
,
在里面我们会把左右表达式再调用Visit,这个又会根据相应的表达式去调用方法,如果是二元表达式那就是调用VisitBinary
,如果是常量就会调用VisitConstant
来解读。所以表达式会上图看到的二叉树解析一样,会一层一层把所有的表达式解析出来。里面还有很多其他类型的解析方法,比如VisitMethodCall
方法表达式,如x.Name.StartsWith("1")
。VisitMember
成员表达式,如:x.Age
。
ExpressionVisitor
访问者类,Visit是个入口,解读表达式的入口上面介绍了,可以通过解析表达式目录树把表达式的每一个节点全部解析出来,解析出来后,怎么实现构造sql条件呢,这个就需要我们自己来按照自己的语法来构造了,我们想象,我们把表达式每一个节点都解析出来,那是不是就可以根据节点来构造sql条件了。编写一个通过解析表达式目录的构造sql语句的工具类:
public class ConditionBuilderVisitor : ExpressionVisitor
{
//使用Stack是为了先进后出的顺序
private Stack<string> _StringStack = new Stack<string>();
public string Condition()
{
string condition = string.Concat(this._StringStack.ToArray());
this._StringStack.Clear();
return condition;
}
///
/// 二元表达式
///
///
///
protected override Expression VisitBinary(BinaryExpression node)
{
if (node == null) throw new ArgumentNullException("BinaryExpression");
this._StringStack.Push(")");
base.Visit(node.Right);//解析右边
this._StringStack.Push(" " + node.NodeType.ToSqlOperator() + " ");
base.Visit(node.Left);//解析左边
this._StringStack.Push("(");
return node;
}
///
///
///
///
///
protected override Expression VisitMember(MemberExpression node)
{
if (node == null) throw new ArgumentNullException("MemberExpression");
this._StringStack.Push(" [" + node.Member.Name + "] ");
return node;
}
///
/// 常量表达式
///
///
///
protected override Expression VisitConstant(ConstantExpression node)
{
if (node == null) throw new ArgumentNullException("ConstantExpression");
this._StringStack.Push(" '" + node.Value + "' ");
return node;
}
///
/// 方法表达式
///
///
///
protected override Expression VisitMethodCall(MethodCallExpression m)
{
if (m == null) throw new ArgumentNullException("MethodCallExpression");
string format;
switch (m.Method.Name)
{
case "StartsWith":
format = "({0} LIKE {1}+'%')";
break;
case "Contains":
format = "({0} LIKE '%'+{1}+'%')";
break;
case "EndsWith":
format = "({0} LIKE '%'+{1})";
break;
default:
throw new NotSupportedException(m.NodeType + " is not supported!");
}
this.Visit(m.Object);
this.Visit(m.Arguments[0]);
string right = this._StringStack.Pop();
string left = this._StringStack.Pop();
this._StringStack.Push(String.Format(format, left, right));
return m;
}
}
调用:
Expression<Func<People, bool>> lambda = x => x.Age > 5
&& x.Id < 5//;
&& x.Id == 3
&& x.Name.StartsWith("1")
&& x.Name.EndsWith("1")
&& x.Name.Contains("1");
//DateTime.Parse Format
string sql = $"Delete From [{typeof(People).Name}] WHERE {"[Age] > 5 AND [ID] >5"}"; ;
ConditionBuilderVisitor vistor = new ConditionBuilderVisitor();
vistor.Visit(lambda);
Console.WriteLine(vistor.Condition());
打印结果:
****************认识表达式目录树*************
********************MapperTest********************
********************解析表达式目录树********************
(((((( [Age] > '5' ) AND ( [Id] < '5' )) AND ( [Id] = '3' )) AND ( [Name] LIKE '1' +'%')) AND ( [Name] LIKE '%'+ '1' )) AND ( [Name] LIKE '%'+ '1' +'%'))
调用:
Expression<Func<People, bool>> lambda = x => x.Age > 5 && x.Name == "A" || x.Id > 5;
ConditionBuilderVisitor vistor = new ConditionBuilderVisitor();
vistor.Visit(lambda);
Console.WriteLine(vistor.Condition());
结果:
((( [Age] > '5' ) AND ( [Name] = 'A' )) OR ( [Id] > '5' ))
调用:
Expression<Func<People, bool>> lambda = x => x.Age > 5 || (x.Name == "A" && x.Id > 5);
ConditionBuilderVisitor vistor = new ConditionBuilderVisitor();
vistor.Visit(lambda);
Console.WriteLine(vistor.Condition());
结果:
(( [Age] > '5' ) OR (( [Name] = 'A' ) AND ( [Id] > '5' )))
调用:
Expression<Func<People, bool>> lambda = x => (x.Age > 5 || x.Name == "A") && x.Id > 5;
ConditionBuilderVisitor vistor = new ConditionBuilderVisitor();
vistor.Visit(lambda);
Console.WriteLine(vistor.Condition());
结果:
((( [Age] > '5' ) OR ( [Name] = 'A' )) AND ( [Id] > '5' ))
至此一个简单通过表达式目录树构造sql条件的工具完成了。
因为整个过程不好通过静态的描述来讲清楚,如果想更加了解这个解析的过程,请自行调试,看看逐步追踪调试,看看这个visit
是怎么递归解析出表达式的每一个节点,这样会对这个解析过程有更深刻的理解。
上一章我们提到一个问题,就是根据用户输入的信息构造条件,我们使用表达式方式的方式,没有解决一个问题就是把多条件输入的条件表达式合并,通过上面的解析知识,下面我们实现这个表达式链接的扩展。
///
/// 合并表达式 And Or Not扩展
///
public static class ExpressionExtend
{
///
/// 合并表达式 expr1 AND expr2
///
///
///
///
///
public static Expression<Func<T, bool>> And<T>(this Expression<Func<T, bool>> expr1, Expression<Func<T, bool>> expr2)
{
//这种方式不能用,因为和导致类型不一样,比如合并两个表达式x.Age> 5 和x.Age<10,这两个表达式的x是不一样的运行会报错。
//return Expression.Lambda>(Expression.AndAlso(expr1.Body, expr2.Body), expr1.Parameters);
if (expr1 == null)
return expr2;
else if (expr2 == null)
return expr1;
ParameterExpression newParameter = Expression.Parameter(typeof(T), "c");
NewExpressionVisitor visitor = new NewExpressionVisitor(newParameter);
var left = visitor.Replace(expr1.Body);
var right = visitor.Replace(expr2.Body);
var body = Expression.And(left, right);
return Expression.Lambda<Func<T, bool>>(body, newParameter);
}
///
/// 合并表达式 expr1 or expr2
///
///
///
///
///
public static Expression<Func<T, bool>> Or<T>(this Expression<Func<T, bool>> expr1, Expression<Func<T, bool>> expr2)
{
if (expr1 == null)
return expr2;
else if (expr2 == null)
return expr1;
ParameterExpression newParameter = Expression.Parameter(typeof(T), "c");
NewExpressionVisitor visitor = new NewExpressionVisitor(newParameter);
var left = visitor.Replace(expr1.Body);
var right = visitor.Replace(expr2.Body);
var body = Expression.Or(left, right);
return Expression.Lambda<Func<T, bool>>(body, newParameter);
}
public static Expression<Func<T, bool>> Not<T>(this Expression<Func<T, bool>> expr)
{
if (expr == null) return null;
var candidateExpr = expr.Parameters[0];
var body = Expression.Not(expr.Body);
return Expression.Lambda<Func<T, bool>>(body, candidateExpr);
}
}
///
/// 建立新表达式
///
internal class NewExpressionVisitor : ExpressionVisitor
{
public ParameterExpression _NewParameter {
get; private set; }
public NewExpressionVisitor(ParameterExpression param)
{
this._NewParameter = param;
}
public Expression Replace(Expression exp)
{
return this.Visit(exp);
}
protected override Expression VisitParameter(ParameterExpression node)
{
return this._NewParameter;
}
}
运行:
Expression<Func<People, bool>> lambda1 = x => x.Age > 5;
Expression<Func<People, bool>> lambda2 = x => x.Id > 5;
Expression<Func<People, bool>> lambda3 = lambda1.And(lambda2);
Expression<Func<People, bool>> lambda4 = lambda1.Or(lambda2);
Expression<Func<People, bool>> lambda5 = lambda1.Not();
Do1(lambda3);
Do1(lambda4);
Do1(lambda5);
private static void Do1(Expression<Func<People, bool>> func)
{
List<People> people = new List<People>()
{
new People(){
Id=4,Name="123",Age=4},
new People(){
Id=5,Name="234",Age=5},
new People(){
Id=6,Name="345",Age=6},
};
List<People> peopleList = people.Where(func.Compile()).ToList();
}