生成SQL語句的功能可以算是LinqToDB框架的最后一步。從上一章中我們可以知道處理完表達式樹之后,相關生成SQL信息會被保存在一個叫SelectQuery類的實例。有了這個實例我們就可以生成對應的SQL語句。想要了解這一步部分的功能就必須從三個方面入手。一、Linq To SQL的機制原理。二、如何生成SQL語句。三、設置映射結果。
生成映射表達式
對於Linq To SQL的機制原理在前面的章節里面已經講過了。這里筆者提出來主要目標是明確什么時候觸發。下面的代碼不是看前面的獲得Query<T>類實列,而是看后面的GetIEnumerable方法調用。
ExpressionQuery<T>類:
IEnumerable<T> Execute(IDataContextInfo dataContextInfo, Expression expression) { return GetQuery(expression, true).GetIEnumerable(null, dataContextInfo, expression, Parameters); }
記得筆者前面幾個章節中講到最后都會去調用倆個方法分別是Query<T>類中的GetIEnumerable方法和GetElement方法。而這倆個方法都是Func類型。如下
public Func<QueryContext, IDataContextInfo, Expression, object[], object> GetElement; public Func<QueryContext, IDataContextInfo, Expression, object[], IEnumerable<T>> GetIEnumerable;
顯然很明顯在調用GetIEnumerable方法一定要知道哪一個方法賦給他了。好了,先暫停一下。讓我們去看一下上一章中筆者講到Build<T>()方法有三個重要方法中的一個——BuildQuery()方法。
1 internal Query<T> Build<T>() 2 { 3 var sequence = BuildSequence(new BuildInfo((IBuildContext)null, Expression, new SelectQuery())); 4 5 if (_reorder) 6 lock (_sync) 7 { 8 _reorder = false; 9 _sequenceBuilders = _sequenceBuilders.OrderByDescending(_ => _.BuildCounter).ToList(); 10 } 11 12 _query.Init(sequence, CurrentSqlParameters); 13 14 var param = Expression.Parameter(typeof(Query<T>), "info"); 15 16 sequence.BuildQuery((Query<T>)_query, param); 17 18 return (Query<T>)_query; 19 }
事實在調用GetIEnumerable方法之前,上面的BuildQuery()方法里面已經對GetIEnumerable進行了賦值一個新的方法。對於BuildQuery()方法只要點擊進去看的話,就會發現他並不是屬於XxxxBuilder類的。而是屬於XxxxBuilder類對應的IBuildContext接口實例。例如下面
TableContext類:
public void BuildQuery<T>(Query<T> query, ParameterExpression queryParameter) { var expr = BuildQuery(typeof(T), this, null); var mapper = Builder.BuildMapper<T>(expr); query.SetQuery(mapper); }
好像沒有發現對於GetIEnumerable進行賦值的代碼。不要緊張我們先看一下這代碼是做什么的。假設我們已經生成SQL語句,也執行了數據庫了。那么得到數據庫的結果又是什么樣子映射成對象類呢?看樣子大家一定明白筆者的意思。沒有錯。這邊就是設置回返結果的映射。同樣子作者也是用表達式來構建一個方法來設置返回對象結果。具體做法讀者可以自己斷點進去看。mapper就是最后生成的映射表達式樹。我們可以看到他做為參數傳給了SetQuery()方法。
Query<T>類:
internal void SetQuery(Expression<Func<QueryContext, IDataContext, IDataReader, Expression, object[], T>> expression) { var query = GetQuery(); var mapInfo = new MapInfo { Expression = expression }; ClearParameters(); GetIEnumerable = (ctx, db, expr, ps) => Map(query(db, expr, ps, 0), ctx, db, expr, ps, mapInfo); }
好,看到這一段代碼。我們可以看到他構建一個MapInfo類。記得筆者前面章節的圖片有出現過。最后數據庫的結果就是通過MapInfo類轉化成相關的對象結果。而這邊我們還可以看到GetIEnumerable被重新賦值了。為什么說是被重新賦值了。因為在Query<T>類的構造函數里就已經對GetIEnumerable賦值過了。讀者們可以在去查看一下。
這個時候我們就是能明白調用GetIEnumerable方法,事實上是在調用上面代碼中的賦值的Map方法。所以很明顯去看Map方法做什么就是明白如何調用作者構建方法。即是上面提到的表達式構建的方法。
生成SQL語句
最后一定要執行數據庫,這一步操作離不開上面講到的GetIEnumerable方法。同時我們也知道GetIEnumerable方法事實上是在調用Map方法。而這個過程中會用到一個叫PreparedQuery類。這個類就是用於執行數據庫的預查詢類。里面存放了生成的SQL語句。
Query<T>類:
1 TE RunQuery<TE>( 2 QueryContext ctx, 3 IDataContextInfo dataContextInfo, 4 Expression expr, 5 object[] parameters, 6 Func<QueryContext, IDataContext, IDataReader, Expression, object[], TE> mapper) 7 { 8 var dataContext = dataContextInfo.DataContext; 9 10 object query = null; 11 12 try 13 { 14 query = SetCommand(dataContext, expr, parameters, 0, true); 15 16 using (var dr = dataContext.ExecuteReader(query)) 17 while (dr.Read()) 18 return mapper(ctx, dataContext, dr, expr, parameters); 19 20 return Array<TE>.Empty.First(); 21 } 22 finally 23 { 24 if (query != null) 25 dataContext.ReleaseQuery(query); 26 27 if (dataContextInfo.DisposeContext) 28 dataContext.Dispose(); 29 } 30 }
上面這段代碼是執行數據庫的入口地方。筆者用紅色標出了執行數據庫之前,所做事情的相關代碼——生成SQL語句。不過我們會發現query不是一個字符串,而是PreparedQuery類實例。那么我們看一下生成PreparedQuery的地方,就能明白生成SQL語句離不開一個叫BasicSqlBuilder類。
DataConnection類:
1 internal PreparedQuery GetCommand(IQueryContext query) 2 { 3 if (query.Context != null) 4 { 5 return new PreparedQuery 6 { 7 Commands = (string[])query.Context, 8 SqlParameters = query.SelectQuery.Parameters, 9 SelectQuery = query.SelectQuery, 10 QueryHints = query.QueryHints, 11 }; 12 } 13 14 var sql = query.SelectQuery.ProcessParameters(); 15 var newSql = ProcessQuery(sql); 16 17 if (!object.ReferenceEquals(sql, newSql)) 18 { 19 sql = newSql; 20 sql.IsParameterDependent = true; 21 } 22 23 var sqlProvider = DataProvider.CreateSqlBuilder(); 24 25 var cc = sqlProvider.CommandCount(sql); 26 var sb = new StringBuilder(); 27 28 var commands = new string[cc]; 29 30 for (var i = 0; i < cc; i++) 31 { 32 sb.Length = 0; 33 34 sqlProvider.BuildSql(i, sql, sb); 35 commands[i] = sb.ToString(); 36 } 37 38 if (!query.SelectQuery.IsParameterDependent) 39 query.Context = commands; 40 41 return new PreparedQuery 42 { 43 Commands = commands, 44 SqlParameters = sql.Parameters, 45 SelectQuery = sql, 46 SqlProvider = sqlProvider, 47 QueryHints = query.QueryHints, 48 }; 49 }
上面紅色部分就是生成SQL語句相關的代碼問部分。對於BasicSqlBuilder類筆者簡單的做一些介紹。做一個初步的了解。想要更深入的了解。最好自己去查看一下代碼。BasicSqlBuilder類根據DML來進行划分的。所以我們可以看到下列方法
1.BuildSelectQuery方法:構建查詢語句。 2.BuildDeleteQuery方法:構建刪除語句。 3.BuildUpdateQuery方法:構建更新語句。 4.BuildInsertQuery方法:構建插入語句。
等等
同時又依據SQL語句的結果分為以下方法。
1.BuildSelectClause:關鍵字SELECT部分的語句。 2.BuildFromClause:關鍵字FROM部分的語句。 3.BuildWhereClause:關鍵字WHERE部分的語句。 4.BuildGroupByClause:關鍵字GROUPBY部分的語句。 等等
BasicSqlBuilder類事實上筆者認為比較簡單。而且筆者的目地都是在引導一種查看源碼的思路。想要從源碼中學習到東西還是要靠自己去分析才行。
結語句
本系列的文章筆者也只能引導到這里了。筆者對本系列的定位就是幫助想要了解LinqToDB框架的人做一個引導和分析思路的工作。正如上面講的想要從源碼中學習到東西還是要靠自己去分析才行。