公司的orm框架在dapper的基础上扩展了一套表达式的方法,当时就研究了一下,把学习过程和结果记录下来,和大家分享。
有人会说重复造轮子没必要,直接上EF。
从我的角度来看重复造轮子的原因有以下三种:
1、研究造轮子的原理
2、轮子不满足现在的开发需要
3、装B
最常用到的无非就是ORM的删查改的条件,ORM就是在ado.Net的基础上封装了一层表达式,最后还是将表达式解析成sql,由ado.Net去执行。
那么我们能将表达式树解析成字符串,那么也能反过来。例如运费系统,在后台设置定义好一套计算规则。例如:对应不同的发货渠道,什么重量取哪个区间的费用,多于哪个阶段的费用还要额外费用。我们可以通过解析这套计算规则拼装好表达式树传入参数进行计算。。。
还有别的在评论补充下。。。
不扯多,现在我们只拿解析表达式树来学习。
首先创建4个属性的Users类
1 namespace CG.ExpressionProject
2 {
3 /// <summary>
4 /// 用户类
5 /// </summary>
6 public class Users
7 {
8 public string Name { get; set; }
9
10 public int Phone { get; set; }
11
12 public int Sex { get; set; }
13
14 public int Age { get; set; }
15 }
16 }
接着,我们从最简单的开始,写一个二元运算表达式,F5调试监控观察。
Expression<Func<Users, bool>> expressionUser = users => users.Name == "SkyChen"
从上图可以看见有很多属性,在表达式主体(属性Body),我们暂时只关注三个属性,Left(左节点)、Right(右节点)和 NodeType (当前节点类型)
表达式主体(users.Name == "SkyChen")是一个二元运算表达式,因此可以将Body转换成 BinaryExpression 类型来访问Left和Right。
Left 和 Right 的 NodeType 分别为 MemberAccess(从字段或属性进行读取的运算)、Constant(常量)。
因此可以将 Left 转换成 MemberExpression 类型来访问 Member 属性,将 Right 转换成 ConstantExpression 类型来访问 Value 属性。具体代码如下:
public static string ResolveExpression(Expression<Func<Users, bool>> expression)
{
var bodyNode = (BinaryExpression)expression.Body;
var leftNode = (MemberExpression)bodyNode.Left;
var rightNode = (ConstantExpression)bodyNode.Right;
return string.Format(" {0} {2} {1} ", leftNode.Member.Name, rightNode.Value, bodyNode.NodeType.TransferExpressionType());
}
TransferExpressionType 是针对部分 ExpressionType 的一个转换。
public static string TransferExpressionType(this ExpressionType expressionType)
{
string type = "";
switch (expressionType)
{
case ExpressionType.Equal:
type = "="; break;
case ExpressionType.GreaterThanOrEqual:
type = ">="; break;
case ExpressionType.LessThanOrEqual:
type = "<="; break;
case ExpressionType.NotEqual:
type = "!="; break;
case ExpressionType.AndAlso:
type = "And"; break;
case ExpressionType.OrElse:
type = "Or"; break;
}
return type;
}
那么。一个最简单的表达式解析成where语句就完成了。
然而,实践工作中,大家都会写相对复杂或者说多个条件的表达式。那么再采用上面的方式是无法确认表达式节点的类型进行转换的。我们可以添加一个Visit方法,根据 NodeType 转换成对应的Expression的类型,从而方法访问对应的属性进行表达式解析。
但是,重写之前,我们得了解一件事,既然叫表达式树,意味着在子节点里,还会有多个节点,如下图:
那么,我们假设,只要是 BinaryExpression(二元运算表达式)就会有多个子节,去访问子节点就是一个递归的过程,而终点就是 MemberExpression 和 ConstantExpression,对应字段名称和常量值的拼接。
下面是代码实现:
public class ExpressionTypeHelper
{
public StringBuilder GeWhere = new StringBuilder(100);
public string Where
{
get { return GeWhere.ToString(); }
}
public void ResolveExpression(Expression<Func<Users, bool>> expression)
{
Visit(expression.Body);
}
public void Visit(Expression expression)
{
switch (expression.NodeType)
{
case ExpressionType.Constant:
VisitConstantExpression(expression);
break;
case ExpressionType.MemberAccess:
VisitMemberExpression(expression);
break;
case ExpressionType.Convert:
VisitUnaryExpression(expression);
break;
default:
VisitBinaryExpression(expression);
break;
}
}
public void VisitUnaryExpression(Expression expression)
{
var e = (UnaryExpression)expression;
Visit(e.Operand);
}
public void VisitBinaryExpression(Expression expression)
{
var e = (BinaryExpression)expression;
GeWhere.Append("(");
Visit(e.Left);
GeWhere.Append(e.NodeType.TransferExpressionType());
Visit(e.Right);
GeWhere.Append(")");
}
public void VisitConstantExpression(Expression expression)
{
var e = (ConstantExpression)expression;
if (e.Type == typeof(string))
{
GeWhere.Append("'" + e.Value + "'");
}
else
{
GeWhere.Append(e.Value);
}
}
public void VisitMemberExpression(Expression expression)
{
var e = (MemberExpression)expression;
GeWhere.Append(e.Member.Name);
}
}
结果如下:
一个基本的表达式解析思路基本实现了,但是!随着自己的orm的完善是不是这么多种的Expression类型都得在Visit方法添一遍,不是的。
ExpressionVisitor类是提供给我们的表达式树解析的帮助类,我们只要定义一个类继承ExpressionVisitor,实现一个 ResolveExpression 入口方法,重写
VisitBinary、VisitConstant、VisitMember方法,代码如下:
public class ExpressionTrasfer : ExpressionVisitor
{
public StringBuilder GeWhere = new StringBuilder(100);
public string Where
{
get { return GeWhere.ToString(); }
}
public void ResolveExpression(Expression<Func<Users, bool>> expression)
{
Visit(expression.Body);
}
protected override Expression VisitBinary(BinaryExpression node)
{
GeWhere.Append("(");
Visit(node.Left);
GeWhere.Append(node.NodeType.TransferExpressionType());
Visit(node.Right);
GeWhere.Append(")");
return node;
}
protected override Expression VisitConstant(ConstantExpression node)
{
if (node.Type == typeof(string))
{
GeWhere.Append("'" + node.Value + "'");
}
else if (node.Type == typeof(int))
{
GeWhere.Append(node.Value);
}
return node;
}
protected override Expression VisitMember(MemberExpression node)
{
GeWhere.Append(node.Member.Name);
return node;
}
}
一个简单的表达式解析大致完成了,当然里面还有很多可以完善,例如值类型的判断,is 还是 = ,VisitMethodCall重写等等。原理就这样,实现我这里就不一一列举。如对大家有帮助,麻烦请推荐,有不足请在下面评论提出,我会一一更改。