• 自己动手实现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讲完的,但是发觉内容多了点,知道你们都喜欢短的,期待下一篇吧。

  • 相关阅读:
    codeforces 671C
    zoj3256
    hit2739
    hdu5737
    2017.1其他简要题解
    hdu5967
    初探插头dp
    11月下旬题解
    CompletableFuture的get和getNow()的区别
    python 镜像
  • 原文地址:https://www.cnblogs.com/MigCoder/p/3726059.html
Copyright © 2020-2023  润新知