自己動手實現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,如下圖

StripQuotes原理

這個時候我們實際需要的是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方法,整個過程如下圖

SelectExpression解析過程

到這里為止,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;
    }
}

讓我們試一下結果~~~

SelectExpression翻譯結果

團長我完成任務了!( •̀ ω •́ )y

本來想着把SelectExpression講完的,但是發覺內容多了點,知道你們都喜歡短的,期待下一篇吧。


免責聲明!

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



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