自己動手實現Expression翻譯器 – Part I


伴隨.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熟悉,起碼能知道它的應用,這里給個學習的鏈接

Expression Tree上手指南

三.找到翻譯入口

3.1 LinqToObjectLinqToSql 的核心接口

 

123_thumb8

其中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

/// <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);
    }
}
最后是對QueryProvider的具體實現
/// <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查詢


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM