伴隨.Net3.5到來的Expression,圍繞着它產生了各種各樣有趣的技術與應用,Linq to object、Linq to sql、Linq to sqllite、Linq to Anything啊~~各種舒爽不側漏。當然Expression的應用肯定不會狹隘到只能在Linq查詢里,只是它本身的性質很適合作為查詢表達。不過本系列的目的只是實現自己的Expression翻譯器,其他不做探討。
一. 明確需求
a) 翻譯什么(Expression)
b) 翻譯成什么(What?)
c) 怎么翻譯(How?)
熟悉一門語言(Expression)的時候,想要翻譯成別的語言,這個時候翻譯成什么就只限制於你掌握的語言數目(Sql?SqlLite?),怎么翻譯則取決於你對語言的熟練程度。
既然作為練習,我們就拿比較通用也比較有實際意義的SQL語言來翻譯好了。
二.熟悉Expression
這里要求的是你對Expression熟悉,起碼能知道它的應用,這里給個學習的鏈接
三.找到翻譯入口
3.1 LinqToObject 和 LinqToSql 的核心接口
其中LinqToObject是直接在IEnumerable<T>接口上添加擴展方法。
LinqToSql則是在IQueryable<T>上做擴展。
我們注意到IQueryable有個Expression的成員,這個就是它的查詢表達式,比如
//query為一個IQueryable對象 //使用query調用GroupBy擴展方法,返回一個新的IQueryable對象,賦值給result //那么result.Expression = query.Expression + "x => GroupBy(x.UserName)" //當然實際不是那么加的,做個比喻而已,總之就是鏈式查詢 var result = query.GroupBy(x => x.UserName);
IQueryProvider像是一個工廠,其兩個CreateQuery方法都接收一個Expression然后返回IQueryable對象,我們在代碼里遍歷IQueryable對象時,其實就是由IQueryable對象將自身的Expression傳遞給IQueryProvider對象翻譯並Excute返回IEnumerator。
OK那么我們的入口很明顯了,只要實現自己的IQueryProvider和IQueryable<T>,就可以使用一大堆針對IQueryable<T>的擴展方法啦。
首先是QueryProvider,它只是初步實現了 IQueryProvider 接口,並留下一些方法給子類實現。
/// <summary> /// Linq集成查詢的數據提供器 /// </summary> public abstract class QueryProvider : IQueryProvider { /// <summary> /// 根據表達式創建一個可查詢對象 /// </summary> public IQueryable CreateQuery(Expression expression) { return (IQueryable)this.Execute(expression); } /// <summary> /// 根據表達式創建一個可查詢對象 /// </summary> public IQueryable<TElement> CreateQuery<TElement>(Expression expression) { return new DbQuery<TElement>(this, expression); } /// <summary> /// 根據表達式執行並返回一個結果對象 /// </summary> object IQueryProvider.Execute(Expression expression) { return this.Execute(expression); } /// <summary> /// 根據表達式執行並返回一個結果對象 /// </summary> TResult IQueryProvider.Execute<TResult>(Expression expression) { return (TResult)this.Execute(expression); } /// <summary> /// 執行表達式並返回結果 /// </summary> public abstract object Execute(Expression expression); /// <summary> /// 翻譯表達式為查詢語句 /// </summary> public abstract string Translate(Expression expression); }
其次是DbQuery
最后是對QueryProvider的具體實現/// <summary> /// 一個可使用Lamdba表達式查詢的數據庫對象 /// </summary> public class DbQuery<T> : IQueryable<T> { private readonly QueryProvider _provider; /// <summary> /// 創建一個可使用Linq集成查詢的對象 /// </summary> public DbQuery(QueryProvider provider) { _provider = provider; this.Expression = Expression.Constant(this); } /// <summary> /// 創建一個可使用Linq集成查詢的對象 /// </summary> public DbQuery(QueryProvider provider, Expression expression) : this(provider) { this.Expression = expression; } IEnumerator<T> IEnumerable<T>.GetEnumerator() { return ((IEnumerable<T>)Provider.Execute(this.Expression)).GetEnumerator(); } public IEnumerator GetEnumerator() { return ((IEnumerable)Provider.Execute(this.Expression)).GetEnumerator(); } public Expression Expression { get; set; } public Type ElementType { get { return typeof(T); } } public IQueryProvider Provider { get { return _provider; } } public override string ToString() { return _provider.Translate(this.Expression); }}/// <summary> /// 提供數據庫對象的Lamdba表達式查詢服務 /// </summary> public class DbQueryProvider : QueryProvider { #region 數據庫對象 private readonly DbConnection _dbConnection; #endregion public DbQueryProvider(DbConnection conn) { _dbConnection = conn; } public override object Execute(Expression expression) { var cmd = _dbConnection.CreateCommand(); cmd.CommandText = this.Translate(expression); return cmd.ExecuteReader(); } public override string Translate(Expression expression) { //先不實現 return string.Empty; } }使用示例如下看到這里整個流程應該都比較清晰了。//初始化一個Provider,並將數據庫連接傳遞 var provider = new DbQueryProvider(conn); //創建一個Query,將provider傳遞給它 var query = new DbQuery<User>(provider); //query.Provider.CreateQuery(query.Where(x => x.UserName == "灰機")) var result = query.Where(x => x.UserName == "灰機"); //result.Provider.Execute(result.Expresson) foreach (var user in result) { Console.WriteLine(user.UserName); }四.基礎工作
4.1 既然是將Expression翻譯為SQL查詢式,那么在我們的項目中就得為SQL語句建模,構建一個DbExpression模塊,能夠更好的映射SQL表達式結構
首先是SQL表達式的類型
/// <summary> /// 數據庫表達式類型 /// </summary> public enum DbExpressionType { Query = 1000, Select, Column, Table, Join }
這里為什么我要讓Query = 1000呢,因為這些DbExpression要跟Expression和諧共處的,算是對Expression的擴展,但是枚舉不支持繼承,那我就用土一點的方法,從很大的值開始(1000),以后用到就強轉咯
/// <summary> /// 列表達式 /// </summary> public class ColumnExpression : Expression { public ColumnExpression(Type type, Expression value, string selectAlias, string columnName, int index) : base((ExpressionType)DbExpressionType.Column, type) { SelectAlias = selectAlias; ColumnName = columnName; Index = index; Value = value; } #region 屬性 /// <summary> /// 值表達式 /// </summary> public Expression Value { get; set; } /// <summary> /// 歸屬的查詢表達式的別名 /// </summary> public string SelectAlias { get; set; } /// <summary> /// 列名 /// </summary> public string ColumnName { get; set; } /// <summary> /// 排序 /// </summary> public int Index { get; set; } #endregion }這里為了讓查詢類的Expression更具有抽象性,我引入了QueryExpression,讓其余DbExpression都繼承它。
/// <summary> /// 代表輸出查詢的表達式(Select、Table、Join等表達式) /// </summary> public abstract class QueryExpression : Expression { protected QueryExpression(ExpressionType expressionType, Type type) : base(expressionType, type) { } /// <summary> /// 查詢的別名 /// </summary> public string Alias { get; set; } /// <summary> /// 查詢的所有列表達式 /// </summary> public virtual IEnumerable<ColumnExpression> Columns { get; set; } /// <summary> /// 查詢的結果類型 /// </summary> public Type ElementType { get; set; } /// <summary> /// 查詢表達式的真正類型 /// </summary> public virtual DbExpressionType ExpressionType { get; set; } /// <summary> /// 查詢的來源 /// </summary> public virtual Expression From { get; set; } /// <summary> /// 查詢表達式的翻譯器 /// </summary> public object Translator { get; set; } /// <summary> /// 擴展(存放翻譯器解析表達式時的必要數據) /// </summary> public object ExData { get; set; } }/// <summary> /// 表達式-數據庫表 /// </summary> public class TableExpression : QueryExpression { /// <summary> /// 初始化一個表示數據庫表引用的表達式 /// </summary> /// <param name="type">表內元素的類型(對應實體類)</param> /// <param name="alias">表的別名</param> /// <param name="name">表的名稱</param> public TableExpression(Type type, string alias, string name) : base((ExpressionType)DbExpressionType.Table, type) { ElementType = type; Alias = alias; Name = name; } /// <summary> /// 表的名稱 /// </summary> public string Name { get; set; } public override DbExpressionType DbExpressionType { get { return DbExpressionType.Table; } } }
/// <summary> /// Select 表達式 /// </summary> public class SelectExpression : QueryExpression { public SelectExpression(Type type, string alias, IEnumerable<ColumnExpression> columns, Expression from, Expression where = null,IEnumerable<ColumnExpression> groupBy = null, IEnumerable<Expression> orderBy = null, object translator = null) : base((ExpressionType)DbExpressionType.Select, type) { ElementType = type; Alias = alias; Columns = columns; From = from; Where = where; GroupBy = groupBy; OrderBy = orderBy; Translator = translator; } #region 屬性 /// <summary> /// Where條件 /// </summary> public Expression Where { get; set; } /// <summary> /// GroupBy /// </summary> public IEnumerable<ColumnExpression> GroupBy { get; set; } /// <summary> /// OrderBy /// </summary> public IEnumerable<Expression> OrderBy { get; set; } public override DbExpressionType DbExpressionType { get { return DbExpressionType.Select; } } #endregion }
/// <summary> /// Join 表達式 /// </summary> public class JoinExpression : QueryExpression { public JoinExpression(Type type, QueryExpression left, QueryExpression right, Expression leftKey, Expression rightKey) : base((ExpressionType)DbExpressionType.Join, type) { Left = left; Right = right; LeftKey = leftKey; RightKey = rightKey; } #region 屬性 /// <summary> /// 左表 /// </summary> public QueryExpression Left { get; set; } /// <summary> /// 右表 /// </summary> public QueryExpression Right { get; set; } /// <summary> /// 左表匹配鍵 /// </summary> public Expression LeftKey { get; set; } /// <summary> /// 右表匹配鍵 /// </summary> public Expression RightKey { get; set; } /// <summary> /// 左別名 /// </summary> public string LeftAlias { get { return Left.Alias; } } /// <summary> /// 右別名 /// </summary> public string RightAlias { get { return Right.Alias; } } public override DbExpressionType DbExpressionType { get { return DbExpressionType.Join; } } #endregion }
OK,以上就是我們翻譯器的基礎模型了,下一節講解如何將Expression與DbExpression互相轉換並構建為SQL查詢