干货!表达式树解析"框架"(2)
最新设计请移步 轻量级表达式树解析框架Faller http://www.cnblogs.com/blqw/p/Faller.html
为了过个好年,我还是赶快把这篇完成了吧
声明
本文内容需要有一定基础的开发人员才可轻松阅读,如果有难以理解的地方可以跟帖询问,但我也不一定能回答你,原因参考文章干货!表达式树解析"框架"(1)
本文中的栗子偏向于结构简单,便于理解,主要将的是思路,而不是具体的解决方案;了解思路之后各位程序猿们可以根据自身需求加以变化,俗话说的好:
授人以(dai)渔(ma)不如授之以(fang)渔(fa)
本文主要想表达的就是一种方法和思路,高玩们如果觉得栗子太简单,请不要做无意义的举动,谢谢合作... ...
书接上文
上文中已经说到了,"框架"的基本成员都已经创建完毕了,其实这时候我们还缺少主要角色:解析器实例,但是不要着急,因为这步是最难,也是最麻烦的
所以我这个框架的主要核心也是在这里,怎样方便灵活的编写`表达式树解析器实例`
下面的内容我尽量说的易懂一些,因为希望照顾到一些程度比较差的同学,如果有人觉得幼稚,请忍耐
好了,大家跟我一起做
建立新项目
建立一个新的项目,引用ExpressionParser项目,然后建立一个实体类

编写演示代码
在Main方法中写一个测试用代码,当然要引用命名空间 (我在项目ExpressionParser中的命名空间是blqw.Linq)
static void Main()
{
Expression<Func<User, bool>> expr = u => u.Age == ;//实例化表达式树
ParserArgs arg = new ParserArgs();//实例化解析用参数
Parser.Where(expr.Body, arg); //调用解析方法,因为所有的Expression<T>都是继承LamdaExpression,所以解析的时候需要调用.Body获得真正的表达式树对象
}
直接调试运行
好吧 看懂的同学可能要说了,不是没写解析器的实例吗,这样运行不是报错了吗
没错,就是让他报错
观察错误信息
果然,不错所料的报错了

仔细观察错误信息,`尚未实现BinaryExpression的解析`,这句话说明了,刚才那个委托中,至少包含一个BinaryExpression,而我们没有对应BinaryExpression的解析器,所以出现了异常
现在需要实现一个,继承ExpressionParser<T>,并将T限定为BinaryExpression
class BinaryExpressionParser : ExpressionParser<BinaryExpression>
{
... ...
}
根据前文中的介绍,解析器的命名如果遵循 xxxExpression + Parser 则会自动被框架识别,并注册;
所以我们类的名称叫 BinaryExpressionParser
现在把断点加在Where方法中
调试分析

注意观察传递进来的expr参数
"很巧"的是,BinaryExpression在前文中也有介绍,其主要属性就是Left Right 和 NodeType
现在需要进行进一步的解析了
Left和Right都是表达式树 Expression对象,所以将他们继续交给框架处理
NodeType才是需要立即处理的,由于他是枚举,所以处理起来相对容易
还有一点,处理的结果保存在哪里呢?
关于这点,暂时我把它放在ParserArgs中的Builder属性中,这个属性是一个StringBuilder
public class ParserArgs
{
public ParserArgs()
{
Builder = new StringBuilder();
} public StringBuilder Builder { get; private set; } public IList<ParameterExpression> Forms { get; set; } public readonly string[] FormsAlias = { "it", "A", "B", "C", "D", "E" };
}
ParserArgs
Ps一下:ParserArgs是一个灵活的对象,在这个`框架`中它没有固定的属性和字段;大家可以根据自己的需要去修改它
private static void Sign(ExpressionType type, ParserArgs args)
{
switch (type)
{
case ExpressionType.And:
case ExpressionType.AndAlso:
args.Builder.Append(" AND");
break;
case ExpressionType.Equal:
args.Builder.Append(" =");
break;
case ExpressionType.GreaterThan:
args.Builder.Append(" >");
break;
case ExpressionType.GreaterThanOrEqual:
args.Builder.Append(" >=");
break;
case ExpressionType.NotEqual:
args.Builder.Append(" <>");
break;
case ExpressionType.Or:
case ExpressionType.OrElse:
args.Builder.Append(" OR");
break;
case ExpressionType.LessThan:
args.Builder.Append(" <");
break;
case ExpressionType.LessThanOrEqual:
args.Builder.Append(" <=");
break;
default:
throw new NotImplementedException("无法解释节点类型" + type);
}
}
处理ExpressionType
所以处理ExpressionType 后,结果直接追加到Builder中
Left和Right继续交给框架处理,最后的代码就是:
class BinaryExpressionParser : ExpressionParser<BinaryExpression>
{
public override void Where(BinaryExpression expr, ParserArgs args)
{
if (ExistsBracket(expr.Left))
{
args.Builder.Append(' ');
args.Builder.Append('(');
Parser.Where(expr.Left, args);
args.Builder.Append(')');
}
else
{
Parser.Where(expr.Left, args);
}
Sign(expr.NodeType, args);
if (ExistsBracket(expr.Right))
{
args.Builder.Append(' ');
args.Builder.Append('(');
Parser.Where(expr.Right, args);
args.Builder.Append(')');
}
else
{
Parser.Where(expr.Right, args);
}
} /// <summary> 判断是否需要添加括号
/// </summary>
private static bool ExistsBracket(Expression expr)
{
var s = expr.ToString();
return s != null && s.Length > && s[] == '(' && s[] == '(';
} private static void Sign(ExpressionType type, ParserArgs args)
{
switch (type)
{
case ExpressionType.And:
case ExpressionType.AndAlso:
args.Builder.Append(" AND");
break;
case ExpressionType.Equal:
args.Builder.Append(" =");
break;
case ExpressionType.GreaterThan:
args.Builder.Append(" >");
break;
case ExpressionType.GreaterThanOrEqual:
args.Builder.Append(" >=");
break;
case ExpressionType.NotEqual:
args.Builder.Append(" <>");
break;
case ExpressionType.Or:
case ExpressionType.OrElse:
args.Builder.Append(" OR");
break;
case ExpressionType.LessThan:
args.Builder.Append(" <");
break;
case ExpressionType.LessThanOrEqual:
args.Builder.Append(" <=");
break;
default:
throw new NotImplementedException("无法解释节点类型" + type);
}
}
... ...
}
再次运行调试
错误又出现了

这次是MemberExpression,继续创建MemberExpressionParser,在Where方法中加断点

首先自动忽略上面2个属性
然后分析一下剩下的几个属性,有那些是有用的,可以参考MSDN
...
根据观察Expression属性是需要进一步解析的,依照惯例,交给`框架` Parser.Where(expr.Expression,args);
Member对应的类型是MemberInfo,这里需要有一点点反射的知识了,至少你需要知道PropertyInfo和FieldInfo都是继承自Member的
So,最终代码如下:
class MemberExpressionParser:ExpressionParser<MemberExpression>
{
public override void Where(MemberExpression expr, ParserArgs args)
{
Parser.Where(expr.Expression, args);
args.Builder.Append('[');
args.Builder.Append(expr.Member.Name);
args.Builder.Append(']');
}
... ...
}
继续运行,异常,添加解析器,断点,分析,编写解析器,... ...

class ParameterExpressionParser:ExpressionParser<ParameterExpression>
{
public override void Where(ParameterExpression expr, ParserArgs args)
{
args.Builder.Append(' ');
args.Builder.Append(expr.Name);
args.Builder.Append('.');
}
... ...
}

class ConstantExpressionParser:ExpressionParser<ConstantExpression>
{
public override void Where(ConstantExpression expr, ParserArgs args)
{
args.Builder.Append(' ');
var val = expr.Value;
if (val == null || val == DBNull.Value)
{
args.Builder.Append("NULL");
return;
}
if (val is bool)
{
args.Builder.Append(val.GetHashCode());
return;
}
var code = (int)Type.GetTypeCode(val.GetType());
if (code >= && code <= ) //如果expr.Value是数字类型
{
args.Builder.Append(val);
}
else
{
args.Builder.Append('\'');
args.Builder.Append(val);
args.Builder.Append('\'');
}
}
... ...
}
第一次运行通过
修改一下Main方法,让他可以输出结果
static void Main()
{ Expression<Func<User, bool>> expr = u => u.Age == ;//实例化表达式树
ParserArgs arg = new ParserArgs();//实例化解析用参数
Parser.Where(expr.Body, arg); //调用解析方法,因为所有的Expression<T>都是继承LamdaExpression,所以解析的时候需要调用.Body获得真正的表达式树对象
Console.WriteLine(arg.Builder.ToString());
}

很显然,已经有一些成果了
让我来构造一个简单的ORM
class ORM
{
public DataSet Where<T>(Expression<Func<T, bool>> expr)
{
var sql = "SELECT * FROM [" + typeof(T).Name + "] ";
ParserArgs a = new ParserArgs();
Parser.Where(expr.Body, a);
sql += expr.Parameters[].Name + " WHERE" + a.Builder.ToString();
Console.WriteLine(sql);
//using (var adp = new SqlDataAdapter(sql, ConnectionString))
//{
DataSet ds = new DataSet();
// adp.Fill(ds);
return ds;
//}
} public ORM(string connectionString)
{
ConnectionString = connectionString;
} public string ConnectionString { get; private set; }
}
因为实际上,我的电脑是不存在这样的数据库的,所以我注释了一部分代码
再修改一下Main中的调用方式
static void Main()
{
ORM db = new ORM("server=192.168.0.96;database=tempdb;uid=sa;pwd=123456");
var ds = db.Where<User>(u => u.Age > && (u.Sex == true || u.Name == "blqw"));
}
打印结果
SELECT * FROM [User] u WHERE u.[Age] > 18 AND ( u.[Sex] = 1 OR u.[Name] = 'blqw')
请按任意键继续. . .

课后练习1
至此,解析已经初见成效了,不过这只是一个最简单的栗子
当然正因为是最简单的,所以遇到稍微复杂一点的表达式的时候就无法继续处理了
而且还有bug
不过,如果你的理解和接受能力很强,那么你可以尝试修改源码,处理以下几种情况
db.Where<User>(u => u.Name != null); //u.Name is not null 而非( u.Name <> null )
db.Where<User>(u => u.Name.StartsWith("bl")); //u.Name like 'bl%'
int[] arr = { , , , , };
db.Where<User>(u => arr.Contains(u.Age)); //u.Age in (13,15,17,19,21)
课后练习2
如果需要使用参数化传递参数,又需要怎样修改源码呢?
本章结束
以上2个练习的答案将在第三章中得到解答
未完待续... ...
文章中出现的源码下载:ExpressionParser2.rar
前文链接:干货!表达式树解析"框架"(1)
后文链接:干货!表达式树解析"框架"(3)
干货!表达式树解析"框架"(2)的更多相关文章
- 干货!表达式树解析"框架"(1)
最新设计请移步 轻量级表达式树解析框架Faller http://www.cnblogs.com/blqw/p/Faller.html 关于我和表达式树 其实我也没有深入了解表达式树一些内在实现的原理 ...
- 干货!表达式树解析"框架"(3)
最新设计请移步 轻量级表达式树解析框架Faller http://www.cnblogs.com/blqw/p/Faller.html 这应该是年前最后一篇了,接下来的时间就要陪陪老婆孩子了 关于表达 ...
- 轻量级表达式树解析框架Faller
有话说 之前我写了3篇关于表达式树解析的文章 干货!表达式树解析"框架"(1) 干货!表达式树解析"框架"(2) 干货!表达式树解析"框架" ...
- 表达式树解析"框架"
干货!表达式树解析"框架"(2) 为了过个好年,我还是赶快把这篇完成了吧 声明 本文内容需要有一定基础的开发人员才可轻松阅读,如果有难以理解的地方可以跟帖询问,但我也不一定能回 ...
- Lambda表达式树解析(下)
概述 前面章节,总结了Lambda树的构建,那么怎么解析Lambda表达式树那?Lambda表达式是一种委托构造而成,如果能够清晰的解析Lambda表达式树,那么就能够理解Lambda表达式要传递的正 ...
- Lambda表达式树解析(下)包含自定义的provider和查询
概述 前面章节,总结了Lambda树的构建,那么怎么解析Lambda表达式树那?Lambda表达式是一种委托构造而成,如果能够清晰的解析Lambda表达式树,那么就能够理解Lambda表达式要传递的正 ...
- 介绍一个可以将Expression表达式树解析成Transact-SQL的项目Expression2Sql
一.Expression2Sql介绍 Expression2Sql是一个可以将Expression表达式树解析成Transact-SQL的项目.简单易用,几分钟即可上手使用,因为博主在设计Expres ...
- C#3.0新增功能10 表达式树 03 支持表达式树的框架类型
连载目录 [已更新最新开发文章,点击查看详细] 存在可与表达式树配合使用的 .NET Core framework 中的类的大型列表. 可以在 System.Linq.Expressions 查 ...
- 借助表达式树感受不一样的CRUD
借助表达式树感受不一样的CRUD Intro 最近有个想法,想不写 sql 语句,做一个类似于 ORM 的东西,自己解析表达式树,生成要执行的 sql 语句,最后再执行 sql 语句,返回相应结果. ...
随机推荐
- Centos 6.5 X64 环境下编译 hadoop 2.6.0 --已验证
Centos 6.5 x64 hadoop 2.6.0 jdk 1.7 protobuf-2.5.0 maven-3.0.5 set environment export JAVA_HOME=/hom ...
- 正定矩阵(positive definite matrix)
设M是n阶方阵,如果对任何非零向量z,都有zTMz> 0,其中zT 表示z的转置,就称M正定矩阵. 正定矩阵在合同变换下可化为标准型, 即对角矩阵. 所有特征值大于零的对称矩阵也是正定矩阵. ...
- encodeURIComponent()中文乱码
可能是Tomcat的编码格式问题 应该在8080端口下设置 <Connector port="8080" protocol="HTTP/1.1" co ...
- iOS drewRect方法
You do not need to override this method if your view sets its content in other ways. By the time thi ...
- 承接 AutoCAD 二次开发 项目
本人有多年的CAD开发经验,独立完成多个CAD二次开发项目.熟悉.net及Asp.net开发技术,和Lisp开发技术. 现在成立了工作室,独立承接CAD二次开发项目.结项后提供源码及开发文档,有需要的 ...
- img及父元素(容器)实现类似css3中的background-size:contain / background-size:cover
img及父元素(容器)实现类似css3中的background-size:contain / background-size:cover <!DOCTYPE html> <html ...
- < meta > 元素
< meta > 元素 概要 标签提供关于HTML文档的元数据.元数据不会显示在页面上,但是对于机器是可读的.它可用于浏览器(如何显示内容或重新加载页面),搜索引擎(关键词),或其他 we ...
- java-并发-同步
浏览以下内容前,请点击并阅读 声明 线程间的通信主要是通过访问以及对象引用字段,这种形式的通信非常高效,但是会产生两种可能的错误:线程干扰和内存一致性错误,反正这些错误的工具就是同步. 然而,同步可能 ...
- Leetcode Maximum Product Subarray
Find the contiguous subarray within an array (containing at least one number) which has the largest ...
- DOS命令详解
DOS命令详解 命令 \? 可以进入命令帮助 1.md命令创建目录. MKDIR [drive:]pathMD [drive:]path 如果命令扩展被启用,MKDIR 会如下改变: 如果需要,MKD ...