自己动手实现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[0]);
var lambda = (LambdaExpression)this.StripQuotes(selectCall.Arguments[1]);
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[0](在这里是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 == 0) 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 = 0;
foreach (var column in select.Columns)
{
if (index++ > 0) _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讲完的,但是发觉内容多了点,知道你们都喜欢短的,期待下一篇吧。

时间: 2024-12-20 01:18:14

自己动手实现Expression翻译器 – Part Ⅲ的相关文章

20、自制翻译器

练习介绍 想不想自己动手做个翻译器呢,一点都不难哦- 就用你学过的post和json,一起试试爬取有道翻译自制翻译器吧?(^ω^?) 要求 实现功能:用户输入英文或中文,程序即可打印出来对应的译文. ps:这个练习不看帮助是完不成了, 1.url去掉_o 2.post数据添加'typoResult': 'false' 1 import requests,json 2 #调用了两个模块.requests负责上传和下载数据,json负责解析. 3 4 5 word = input('你想翻译什么呀?

自己动手写ORM的感受

之前看到奋斗前辈和时不我待前辈的自己动手写ORM系列博客,感觉讲解的通俗易懂,清晰透彻.作为一个菜鸟,闲来也想着自己写一个ORM,一来加深自己对 ORM的理解,以求对EF,NHibernate等ROM框架的使用能更加轻车熟路.二来也可在写ORM之时熟悉反射的应用场景,反射的优缺点,优化方 法,Lambda表达式,表达式树等.,对自己也是一个不错的锻炼. ORM的原理也就表映射,反射,拼接sql,缓存,Lambda进行方法调用.网上有很多源码参考和原理讲解,对着敲一敲完成一个简易的ORM并不是什么

atitit.自己动手开发编译器and解释器(2) ------语法分析,语义分析,代码生成--attilax总结

atitit.自己动手开发编译器and解释器(2) ------语法分析,语义分析,代码生成--attilax总结 1. 建立AST 抽象语法树 Abstract Syntax Tree,AST) 1 2. 建立AST 语法树----递归下降(recursive descent)法 2 3. 语法分析概念 2 3.1. 上下文无关语言,非终结符(nonterminal symbol),终结符(terminal symbol).注 2 3.2. 最左推导.当然也有最右推导 3 3.3. 分支预测的

【龙书笔记】用Python实现一个简单数学表达式从中缀到后缀语法的翻译器(采用递归下降分析法)

上篇笔记介绍了语法分析相关的一些基础概念,本篇笔记根据龙书第2.5节的内容实现一个针对简单表达式的后缀式语法翻译器Demo. 备注:原书中的demo是java实例,我给出的将是逻辑一致的Python版本的实现. 在简单后缀翻译器代码实现之前,还需要介绍几个基本概念. 1. 自顶向下分析法(top-down parsing) 顾名思义,top-down分析法的思路是推导产生式时,以产生式开始符号作为root节点,从上至下依次构建其子节点,最终构造出语法分析树.在具体实现时,它会把输入字符串从左到右

失败的尝试 10. regular expression matching &amp; 正则

Regular Expression Matching 看到正则就感觉头大,因为正则用好了就很强大.有挑战的才有意思. 其实没有一点思路.循环的话,不能一一对比,匹配模式解释的是之前的字符.那就先遍历模式把. ... 中间 n 次失败的提交 感觉代码逻辑很乱.重新捋一下再动手写. 找几个重点分析一下: Wrong Answer: Input: "aaa" "ab*a*c*a" Output: false Expected: true 调试 aaa ab*a*c*a

动手造轮子:实现一个简单的依赖注入(一)

动手造轮子:实现一个简单的依赖注入(一) Intro 在上一篇文章中主要介绍了一下要做的依赖注入的整体设计和大概编程体验,这篇文章要开始写代码了,开始实现自己的依赖注入框架. 类图 首先来温习一下上次提到的类图 服务生命周期 服务生命周期定义: public enum ServiceLifetime : sbyte { /// <summary> /// Specifies that a single instance of the service will be created. /// &

编译原理--语法制导翻译器(一)

本章重点在前端,特别是词法分析,语法分析和中间代码生成 首先建立一个将中缀算术表达式转换成后缀表达式的语法制导翻译器,然后我们扩展这个翻译器,将某些程序片段转换为如图所示三地址代码 { int i; int j; float[100] a; float v; float x; while(true){ do i = i + 1; while(a[i] < v); do j = j + 1; while(a[j] > v); if(i >= j) break; x = a[i]; a[i]

LeetCode 10. Regular Expression Matching

https://leetcode.com/problems/regular-expression-matching/description/ Implement regular expression matching with support for '.' and '*'. '.' Matches any single character. '*' Matches zero or more of the preceding element. The matching should cover

Spring AOP中pointcut expression表达式解析 及匹配多个条件

Pointcut 是指那些方法需要被执行"AOP",是由"Pointcut Expression"来描述的. Pointcut可以有下列方式来定义或者通过&& || 和!的方式进行组合. args() @args() execution() this() target() @target() within() @within() @annotation 其中 execution 是用的最多的,其格式为: execution(modifiers-pat