自己动手实现Expression翻译器 – Part Ⅲ
上一节实现了对TableExpression的解析,通过反射创建实例以及构建该实例的成员访问表达式生成了一个TableExpression,并将其遍历格式化为”Select * From TableName ”之类的SQL语句,本节继续对其他QueryExpression进行解析。
先回顾一下几个类的作用
ExpressionVisitor -- 用于遍历Expression
DbExpressionVisitor -- 继承自ExpressionVisitor,并提供DbExpression的遍历支持,在遍历的过程中生成QueryExpression。
QueryFormatter -- 继承自DbExpressionVisitor,重写其对DbExpression的遍历方法,在遍历的过程中生成SQL语句。
一.别名生成
在一个多重子查询的语句里,每一个查询都可以指定别名,形如 “Select * From [User] As user”、“Select * From (Select * From [User] As t0) As t1”这种,类似于Linq中的let子句吧,这是每一个QueryExpression的标识,我们需要能够按一定规则自动生成它。
Linq To Sql是按t0、t1、t2….tn这样生成查询的别名,我们也学它好了,往DbExpressionVisitor里加个属性
#region 表名生成管理 private int _tableIndex; /// <summary>
/// 获取新的查询别名
/// </summary>
public string NewAlias
{
get { return "t" + _tableIndex++; }
} #endregion
二.SelectExpression
SelectExpression与TableExpression的不同在于SelectExpression可以控制更多条件(Where、Top、Distinct),以及定制自身的ColumnExpression,可以将毫不相干的值临时作为列。
先来重点解决无任何附加条件单纯的Select吧,就query.Select( x => x)好了,它应该解析成 “Select * From (Select * From TableName)”之类的。
不用调试我也知道这是一个MethodCallExpression,那我们重写DbExpressionVisitor.VisitMethodCall
protected override Expression VisitMethodCall(MethodCallExpression node)
{
var method = node.Method;
switch (method.Name)
{
case "Select":
return this.VisitSelectCall(node);
} return node;
}/// <summary>
/// 去除表达式中的参数引用包装
/// </summary>
public Expression StripQuotes(Expression e)
{
//如果为参数应用表达式
while (e.NodeType == ExpressionType.Quote)
{
//将其转为一元表达式即可获取真正的值
e = ((UnaryExpression)e).Operand;
}
return e;
} public Expression VisitSelectCall(MethodCallExpression selectCall)
{
var source = (QueryExpression)this.Visit(selectCall.Arguments[]);
var lambda = (LambdaExpression)this.StripQuotes(selectCall.Arguments[]);
var selector = (SelectExpression)this.Visit(lambda.Body); if (selector != null)
{
selector.From = source;
return selector;
} return selectCall;
}StripQuotes方法的用处是去除表达式中的参数引用包装 ,因为 query.Select( x => x) 中的x => x此时被包装为一个QuoteExpression,如下图
这个时候我们实际需要的是Operand这个Lambda表达式 x => x,所以这个方法就是去除这个参数包装,把表达式拿出来而已。
VisitSelectCall方法很好理解
1. 首先Visit一下selectCall.Arguments[](在这里是query对象),那么对query对象Visit的结果是什么呢?就是一个TableExpression了。
2. 其次使用StripQuotes方法得到一句Lambda。
3. 接着Visit这个LambdaExpression的Body部分(x),得到什么呢?我也不知道,不过这个 x 现在是一个ParameterExpression,让我们去重写VisitParameter~
protected override Expression VisitParameter(ParameterExpression param)
{
//Todo:应该生成一个SelectExpression,其列为所有param.Type的成员
return base.VisitParameter(param);
}
╮( ̄▽ ̄")╭去你十三姨的Todo。
首先从上下文来看,param代表的就是要Select一个类型的所有属性列,这个类型从哪来?
从上一个QueryExpression对象中来,也就是query对象的ElementType是什么,param就是它的一个表达式,可以从param.Type得到这个ElementType,这个时候我们还是反射param.Type去生成ColumnExpression吗?
没必要,我们只需要从上一个QueryExpression对象生成的Columns中拿取就好了,也就是每次生成一个QueryExpression对象,都将它的Columns缓存起来,后边如果有引用可以直接拿取。
那么给我们的DbExpressionVisitor动一下手术吧,先加入缓存
/// <summary>
/// 最后一次构建QueryExpression时生成的列集合
/// </summary>
private Dictionary<string, ColumnExpression> _lastColumns =
new Dictionary<string, ColumnExpression>();每次生成QueryExpression都去把这个缓存重赋值一次,那我们回去重写下生成TableExpression的那个方法
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; //生成TableExpression,并将其Columns属性缓存
var tableExp = new TableExpression(queryable.ElementType, string.Empty, tableName);
_lastColumns = tableExp.Columns.ToDictionary(x => x.ColumnName); return tableExp;
} return base.VisitConstant(constant);
}
好了现在来真正的实现VisitParameter方法吧
protected override Expression VisitParameter(ParameterExpression param)
{
//如果缓存中没有任何列
if (_lastColumns.Count == ) return base.VisitParameter(param); var alias = this.NewAlias; //根据_lastColumns中生成newColumns,Value = Expression.Constant(oldColumn)也就是对oldColumn的一个引用
var newColumns = _lastColumns.Values.Select(oldColumn =>
new ColumnExpression(oldColumn.Type,
Expression.Constant(oldColumn),
alias,
oldColumn.ColumnName,
oldColumn.Index)).ToList(); //将生成的新列赋值给缓存
_lastColumns = newColumns.ToDictionary(x => x.ColumnName); return new SelectExpression(param.Type, alias, newColumns.AsReadOnly(), null);
}
最后结果返回到VisitSelectCall方法,整个过程如下图
到这里为止,query.Select( x => x) 被解析成了一个SelectExpression,它的From就是query。
好了让我们迫不及待的去翻译SelectExpression吧
让QueryFormatter去重写VisitSelect方法
public override Expression VisitSelect(SelectExpression select)
{
_sb.Append("SELECT ");
int index = ;
foreach (var column in select.Columns)
{
if (index++ > ) _sb.Append(", ");
this.VisitColumn(column);
} if (select.From != null)
{
_sb.Append(" FROM ");
if (!(select.From is TableExpression)) _sb.Append("(");
this.Visit(select.From);
if (!(select.From is TableExpression)) _sb.Append(")");
}
_sb.AppendFormat(" As {0} ", select.Alias); return select;
}
然后再往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; //新加入对Value为ColumnExpression类型的处理
case (ExpressionType)DbExpressionType.Column:
_sb.AppendFormat("[{0}].[{1}]", column.SelectAlias, column.ColumnName);
break;
default:
this.Visit(column.Value);
_sb.AppendFormat(" As [{0}]", column.ColumnName);
break;
}
}
让我们试一下结果~~~
团长我完成任务了!( •̀ ω •́ )y
本来想着把SelectExpression讲完的,但是发觉内容多了点,知道你们都喜欢短的,期待下一篇吧。
自己动手实现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 Ⅱ
上一节我们了解了Linq查询大体上是如何运转的,并针对SQL表达式进行建模(DbExpression),这一节的重点在于如何将表达式转换为DbExpression. 可以说只要能生成结构清晰的DbEx ...
- 动手写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 ...
随机推荐
- 跟Bob大叔观OO原则
上篇总结了经典的23种 设计模式,详细的解读后期会陆续的详细揭开.使用设计模式的根本原因就是为了增强代码的复用性和可维护性.而面向对象是实现代码复用的有效途径,所以这里有必要了解一下OO的基本思想和原 ...
- TortoiseGit安装与配置(转)
TortoiseGit 简称 tgit, 中文名海龟Git. 海龟Git只支持神器 Windows 系统, 有一个前辈海龟SVN, TortoiseSVN和TortoiseGit都是非常优秀的开源的版 ...
- 网络编程easy错误点-手知道
通常的网络编程socket编程.实际上.socket编程并不仅仅是满足网络间不同主机之间的通信,它也能实现同一台主机上不同进程间的通信需求. 其体如今创建socket时的參数的不同: int sock ...
- 第3章 抽象工厂模式(Abstract Factory)
原文 第3章 抽象工厂模式(Abstract Factory) 场景我们的系统要同时支持两个数据库 SqlServer 跟Oracle数据库 并且不同的环境要进行随时切换. 看下面的代码: 1 2 ...
- 数学思想方法-分布式计算-linux/unix技术基础(3)
夹: ~表示当前用户的主文件夹 .它代表了当前文件夹 ..它代表的父文件夹 链接文件 使用不同的文件名指的是相同的数据或程序.硬链接 在相同的物理文件系统,创建一个硬链接 -bash-4.2$ fin ...
- CSS3之重新定义鼠标右键
效果图: html: <div id="rightkey"> <ul> <li><img src="images/xmgl.pn ...
- linux_ssky-keygen + ssh-copy-id 无密码登陆远程LINUX主机
使用下例中ssky-keygen和ssh-copy-id,仅需通过3个步骤的简单设置而无需输入密码就能登录远程Linux主机. ssh-keygen 创建公钥和密钥. ssh-copy-id 把本地主 ...
- Java业务原子性的一种实现(key 独占访问)
开发过程中,有时候为了解决多线程竞争问题需要加锁,通常锁定的对象是class,object,method,但在特定时候我们需要更细粒度的加锁,也就是根据不同输入参数来锁定不同的资源,这样只有调用此方法 ...
- CSS3+HTML5特效1 - 上下滑动效果
先看看效果,把鼠标移上去看看. back front 1. 本实例需要以下元素: a. 外容器 box b. 内容器 border c. 默认显示内容 front d. 滑动内容 back 2. 外容 ...
- Php设计模式(三):行为型模式part2
原文详见:http://www.ucai.cn/blogdetail/7023?mid=1&f=5 可以在线运行查看效果哦! <接上文> 5.中介者模式(Mediator) : 用 ...


