上一節我們了解了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 = 0; _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 = 0; foreach (var column in table.Columns) { if (index++ > 0) _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
四.下一步做什么
我還沒想好,等我對代碼稍作整理~