自己动手实现Expression翻译器 – Part Ⅱ
上一节我们了解了Linq查询大体上是如何运转的,并针对SQL表达式进行建模(DbExpression),这一节的重点在于如何将表达式转换为DbExpression。
可以说只要能生成结构清晰的DbExpression,我们的翻译器就已经成功了一半了。为了将表达式转换为DbExpression,我们需要遍历它们,分解它们,在这个过程中拿出我们需要的信息,去构建一个个符合逻辑的DbExpression对象,最终将这些DbExpression组合起来,就形成了一个结构清晰的查询。
一.表达式遍历器(ExpressionVisitor)
相信不少人研究过ExpressionVisitor,它的作用是输入一个Expression,将Expression拆解为各个组成部分(也是Expression),再针对这些组成部分继续拆解,直到无法拆解,这是一个自顶向下的过程,每次拆解都是根据ExpressionType的不同而去调用专用的方法。
比如 x.Name == “灰机”这个BinaryExpression,将其输入ExpressionVisitor的执行过程如下
其中每个Visit方法都会将解析的结果返回,没有重写这些方法的话,ExpressionVisitor.Visit(Exp)的结果基本等于 Exp本身的,只是将它拆开又组合了一遍。
ExpressionVisitor帮我们做了很多工作,能准确的将特定类型的Expression传递给特定类型的方法去解析,并且它这些解析方法都是可重写的,我们就继承它,实现自己的解析逻辑。
二.数据库表达式遍历器(DbExpressionVisitor)
/// <summary>
/// 数据库表达式遍历器
/// </summary>
public class DbExpressionVisitor : ExpressionVisitor
{
}
首先重写Visit方法提供对DbExpression的遍历支持
public override Expression Visit(Expression exp)
{
if (exp == null) return null; switch ((DbExpressionType)exp.NodeType)
{
case DbExpressionType.Select:
case DbExpressionType.Table:
case DbExpressionType.Join:
case DbExpressionType.Query:
return this.VisitQuery((QueryExpression)exp);
case DbExpressionType.Column:
return this.VisitColumn((ColumnExpression)exp);
} return base.Visit(exp);
}
1.我们需要完成基础的对IQueryable对象的解析,也就是说对一个IQueryable对象,没有调用任何方法,我们的解析器应该解析出一个QueryExpression对象
先从最普通的TableExpression开始
//query代表的是对User表的整表查询,应该解析为TableExpression
var query = new DbQuery<User>(provider);
先调试一下Execute方法看看query的Expression长啥样
可以发现是一个ConstantExpression,那我们就去改写VisitConstant方法
protected override Expression VisitConstant(ConstantExpression constant)
{
var queryable = constant.Value as IQueryable;
if (queryable != null)
{
//TableAttribute用来描述类对应的数据库表信息
var table = (TableAttribute)queryable.ElementType.GetCustomAttributes(typeof(TableAttribute), false).FirstOrDefault();
//如果没有该特性,直接使用类名作为表名
var tableName = table == null ? queryable.ElementType.Name : table.Name; return new TableExpression(queryable.ElementType, string.Empty, tableName);
} return base.VisitConstant(constant);
}使用示例如下
这样我们就得到了一个结构清晰的TableExpression对象,根据这个对象生成SQL语句是不是很容易?容易得很~Name、Columns都有,等等,Columns我们没生成啊,我们只是得到了个表名啊喂,OK那我们就来做ColumnExpression的生成
快速回顾一下一个ColumnExpression的必要元素有哪些
SelectAlias -- 在SelectExpression中就是Select的别名,在TableExpression中应该是表名。
ColumnsName -- 列名
Index -- 排序
为什么需要Value呢,使用SelectAlias.ColumnName不就可以标识出列的全部信息了吗?
这是因为有时候列不一定是从来源那里得到的,比如我可以
”query.Select( x => new { Date = DateTime.Now, x.UserName } )“
这个时候[Date]列是我们临时构建的,我们需要保存DateTime.Now到Value里边去,而[UserName]的Value应该是对[User]的一个列引用
既然TableExpression代表对一个表的全部列查询,那我们就生成这个表的所有ColumnExpression好了,从哪里的得到这些信息呢?从映射类的定义
[Table(Name = "User")]
public class User
{
[Column(IsPrimaryKey = true)]
public int UserId { get; set; } [Column]
public string UserName { get; set; }
}
这些信息从TableExpression.Type就可以得到了,上代码(重载了QueryExpression.Columns)
/// <summary>
/// 表的列
/// </summary>
public override IEnumerable<ColumnExpression> Columns
{
get
{
if (_columns == null)
{
int index = ;
_columns = new List<ColumnExpression>();
var members = Type.GetProperties();
var obj = Expression.Constant(Activator.CreateInstance(Type));
foreach (var member in members)
{
_columns.Add(new ColumnExpression(member.PropertyType,
Expression.MakeMemberAccess(obj, member), Alias, member.Name, index++));
}
} return _columns;
}
} private List<ColumnExpression> _columns;
当然这里有很多可以优化的地方,但是现在先让我们看看使用上述代码后生成的结果
注意SelectAlias我赋了空,因为别名需要另外一些逻辑才能确定,不能单纯使用表名。
这里每个ColumnExpression的Value都是一条MemberExpression,这样我们就得到了详细的元数据了。
三.数据库表达式格式化器(QueryFormatter)
扯了这么多,终于可以写点有用的了,我们需要对TableExpression再Visit一遍,这次的是要生成SQL语句了。
首先为了职责分明,让我们再引入一个格式化器~
/// <summary>
/// 查询语句格式化器
/// </summary>
public class QueryFormatter : DbExpressionVisitor
{
}
内部放置一个StringBuilder来拼接语句
private readonly StringBuilder _sb = new StringBuilder();重载一下VisitTable
public override Expression VisitTable(TableExpression table)
{
_sb.Append("(SELECT ");int index = ;
foreach (var column in table.Columns)
{
if (index++ > ) _sb.Append(", ");
this.VisitColumn(column);
}
_sb.AppendFormat(" FROM [{0}]", table.Name);if (!table.Alias.IsNullOrEmpty())
_sb.AppendFormat(" As [{0}] ", table.Alias);_sb.Append(")"); return table;
}
再重载一下VisitColumn
public override Expression VisitColumn(ColumnExpression column)
{
var value = column.Value;
switch (value.NodeType)
{
case ExpressionType.MemberAccess:
if (!column.SelectAlias.IsNullOrEmpty())
_sb.AppendFormat("[{0}].", column.SelectAlias); var member = ((MemberExpression)value).Member;
if (member.Name == column.ColumnName)
_sb.AppendFormat("[{0}]", column.ColumnName);
else
_sb.AppendFormat("[{0}] As [{1}]", member.Name, column.ColumnName);
break;
default:
this.Visit(column.Value);
_sb.AppendFormat(" As [{0}]", column.ColumnName);
break;
} return column;
}只要将TableExpression传递给QueryFormatter.Visit(),QueryFormatter内部的_sb对象就拥有了完整的SQL语句,让我们开放一个方法给外部调用验证下结果先
public string Format(Expression expression)
{
_sb.Clear();
this.VisitQuery((QueryExpression)expression);
return this._sb.ToString();
} public override Expression VisitQuery(QueryExpression exp)
{
switch (exp.DbExpressionType)
{
case DbExpressionType.Select: this.VisitSelect((SelectExpression)exp); break;
case DbExpressionType.Table: this.VisitTable((TableExpression)exp); break;
case DbExpressionType.Join: this.VisitSource((JoinExpression)exp); break;
} return exp;
}
这里假设Expression已经被转换为QueryExpression,毕竟没有转换的话我们这个格式化器是无法运转的。
调用示例如下
团长我完成任务了!( •̀ ω •́ )y
四.下一步做什么
我还没想好,等我对代码稍作整理~
自己动手实现Expression翻译器 – Part Ⅱ的更多相关文章
- 动手实现Expression翻译器1
动手实现Expression翻译器 – Part I 伴随.Net3.5到来的Expression,围绕着它产生了各种各样有趣的技术与应用,Linq to object.Linq to sql.L ...
- 自己动手实现Expression翻译器 – Part I
伴随.Net3.5到来的Expression,围绕着它产生了各种各样有趣的技术与应用,Linq to object.Linq to sql.Linq to sqllite.Linq to Anythi ...
- 自己动手实现Expression翻译器 – Part Ⅲ
上一节实现了对TableExpression的解析,通过反射创建实例以及构建该实例的成员访问表达式生成了一个TableExpression,并将其遍历格式化为”Select * From TableN ...
- 动手写IL到Lua的翻译器——准备
文章里的代码粘过来的时候格式有点问题,原因是一开始文章是在订阅号上写的(gamedev101,文末有二维码),不知道为啥贴过来就没了格式,还要手动删行号,就没搞了. 介绍下问题背景: 小说君正在参与的 ...
- 自己动手写ORM的感受
之前看到奋斗前辈和时不我待前辈的自己动手写ORM系列博客,感觉讲解的通俗易懂,清晰透彻.作为一个菜鸟,闲来也想着自己写一个ORM,一来加深自己对 ORM的理解,以求对EF,NHibernate等ROM ...
- Windows 8 动手实验系列教程 实验8:Windows应用商店API
动手实验 实验 8: Windows应用商店API 2012年9月 简介 编写Windows应用商店应用最令人瞩目的理由之一是您可以方便地将它们发布到Windows应用商店.考虑到世界范围内目前有超过 ...
- 不可不知的表达式树(1)Expression初探
说起Lambda表达式,大家基本都很熟悉了,而表达式树(Expression Trees),则属于80%的工作中往往都用不到的那种技术,所以即便不是什么新技术,很多人对其理解都并不透彻.此文意图从表达 ...
- LeetCode 失败的尝试 10. regular expression matching & 正则
Regular Expression Matching 看到正则就感觉头大,因为正则用好了就很强大.有挑战的才有意思. 其实没有一点思路.循环的话,不能一一对比,匹配模式解释的是之前的字符.那就先遍历 ...
- atitit.自己动手开发编译器and解释器(2) ------语法分析,语义分析,代码生成--attilax总结
atitit.自己动手开发编译器and解释器(2) ------语法分析,语义分析,代码生成--attilax总结 1. 建立AST 抽象语法树 Abstract Syntax Tree,AST) 1 ...
随机推荐
- hdu4419 Colourful Rectangle 12年杭州网络赛 扫描线+线段树
题意:给定n个矩形,每个矩形有一种颜色,RGB中的一种.相交的部分可能为RG,RB,GB,RGB,问这n个矩形覆盖的面积中,7种颜色的面积分别为多少 思路:把x轴离散化做扫描线,线段树维护一个扫描区间 ...
- JQuery之初探
软考过后又进入了紧张的B/S学习阶段,因为自己的进度比較慢,所以更要加进学习.如今就来总结下JQuery的一些基础知识: JQuery定义 jQuery是一套跨浏览器的JavaScript库,简化HT ...
- Winform 实现像菜单一样弹出层
原文:Winform 实现像菜单一样弹出层 在实际工作中,如果能像菜单一样弹出自定义内容,会方便很多,比如查询时,比如下拉列表显示多列信息时,比如在填写某个信息需要查看一些信息树时.这个时候自定义弹出 ...
- hdu Oulipo(kmp)
Problem Description The French author Georges Perec (1936–1982) once wrote a book, La disparition, w ...
- 转让lua性能executeGlobalFunction
没有其他的,搞搞cocos2dx的lua文字,话lua这件事情在几年前学过一段时间.还曾对自己c++介面,我已经做了一些小东西.只是时间的流逝,模糊记忆. 拿起点功夫和成本.下面是我的一些经验. co ...
- android_Activity生命周期功能
说明:初步activity 生命周期7功能. 样本:于MainActivity我加了button,搬家button.跳到OtherActivity.控制台输出的观察. 让我们来看看这些功能: 他们的流 ...
- MyEclipse10.0 集成 SVN
一:下载服务端和client工具 服务端安装工具:Setup-Subversion-1.6.5.msi client安装工具:TortoiseSVN 下载地址:http://subclipse.t ...
- linux_vim_最佳快捷键
如何使用vi文本编辑器 vi由比尔·乔伊(Bill Joy)撰写,所有UNIX like均默认安装此文本编辑器.详细简介请点击维基中文. 1.首先复制一个文件到/tmp目录(本例中为复制根目录 ...
- uva757 - Gone Fishing(馋)
题目:uva757 - Gone Fishing(贪心) 题目大意:有N个湖泊仅仅有一条通路将这些湖泊相连. 每一个湖泊都会给最開始5分钟间隔内能够调到的鱼(f).然后给每过5分钟降低的鱼的数量(d) ...
- Topcoder SRM 628 DIV 2
被自己蠢哭了.... 250-point problem 国际象棋棋盘上给出两个坐标,问象从一个走到还有一个最少要几步. 黑格象仅仅能走黑格,白格象仅仅能走白格,仅仅要推断两个坐标的颜色是否同样就能推 ...