• 自己动手实现Expression翻译器 – Part Ⅱ


    上一节我们了解了Linq查询大体上是如何运转的,并针对SQL表达式进行建模(DbExpression),这一节的重点在于如何将表达式转换为DbExpression

    可以说只要能生成结构清晰的DbExpression,我们的翻译器就已经成功了一半了。为了将表达式转换为DbExpression,我们需要遍历它们,分解它们,在这个过程中拿出我们需要的信息,去构建一个个符合逻辑的DbExpression对象,最终将这些DbExpression组合起来,就形成了一个结构清晰的查询。

    一.表达式遍历器(ExpressionVisitor)

    相信不少人研究过ExpressionVisitor,它的作用是输入一个Expression,将Expression拆解为各个组成部分(也是Expression),再针对这些组成部分继续拆解,直到无法拆解,这是一个自顶向下的过程,每次拆解都是根据ExpressionType的不同而去调用专用的方法。

    比如 x.Name == “灰机”这个BinaryExpression,将其输入ExpressionVisitor的执行过程如下

    Visitor过程

    其中每个Visit方法都会将解析的结果返回,没有重写这些方法的话,ExpressionVisitor.Visit(Exp)的结果基本等于 Exp本身的,只是将它拆开又组合了一遍。

    ExpressionVisitor帮我们做了很多工作,能准确的将特定类型的Expression传递给特定类型的方法去解析,并且它这些解析方法都是可重写的,我们就继承它,实现自己的解析逻辑。

    二.数据库表达式遍历器(DbExpressionVisitor)

        /// <summary>
        /// 数据库表达式遍历器
        /// </summary>
        public class DbExpressionVisitor : ExpressionVisitor
        {
        }

    首先重写Visit方法提供对DbExpression的遍历支持

    public override Expression Visit(Expression exp)
    {
        if (exp == null) return null;
    
        switch ((DbExpressionType)exp.NodeType)
        {
            case DbExpressionType.Select:
            case DbExpressionType.Table:
            case DbExpressionType.Join:
            case DbExpressionType.Query:
                return this.VisitQuery((QueryExpression)exp);
            case DbExpressionType.Column:
                return this.VisitColumn((ColumnExpression)exp);
        }
    
        return base.Visit(exp);
    }

    1.我们需要完成基础的对IQueryable对象的解析,也就是说对一个IQueryable对象,没有调用任何方法,我们的解析器应该解析出一个QueryExpression对象

    先从最普通的TableExpression开始

    //query代表的是对User表的整表查询,应该解析为TableExpression
    var query = new DbQuery<User>(provider);

    先调试一下Execute方法看看query的Expression长啥样

    IQueryable.Expression

    可以发现是一个ConstantExpression,那我们就去改写VisitConstant方法

    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;
    
            return new TableExpression(queryable.ElementType, string.Empty, tableName);
        }
    
        return base.VisitConstant(constant);
    }

    使用示例如下

    TableExpression示例

    这样我们就得到了一个结构清晰的TableExpression对象,根据这个对象生成SQL语句是不是很容易?容易得很~Name、Columns都有,等等,Columns我们没生成啊,我们只是得到了个表名啊喂,OK那我们就来做ColumnExpression的生成

    快速回顾一下一个ColumnExpression的必要元素有哪些

    image

    SelectAlias -- 在SelectExpression中就是Select的别名,在TableExpression中应该是表名。

    ColumnsName -- 列名

    Index -- 排序

    为什么需要Value呢,使用SelectAlias.ColumnName不就可以标识出列的全部信息了吗?

    这是因为有时候列不一定是从来源那里得到的,比如我可以

    ”query.Select( x => new {  Date = DateTime.Now,  x.UserName } )“

    这个时候[Date]列是我们临时构建的,我们需要保存DateTime.Now到Value里边去,而[UserName]的Value应该是对[User]的一个列引用

    既然TableExpression代表对一个表的全部列查询,那我们就生成这个表的所有ColumnExpression好了,从哪里的得到这些信息呢?从映射类的定义

    [Table(Name = "User")]
    public class User
    {
        [Column(IsPrimaryKey = true)]
        public int UserId { get; set; }
    
        [Column]
        public string UserName { get; set; }
    }

    这些信息从TableExpression.Type就可以得到了,上代码(重载了QueryExpression.Columns)

    /// <summary>
    /// 表的列
    /// </summary>
    public override IEnumerable<ColumnExpression> Columns
    {
        get
        {
            if (_columns == null)
            {
                int index = 0;
                _columns = new List<ColumnExpression>();
                var members = Type.GetProperties();
                var obj = Expression.Constant(Activator.CreateInstance(Type));
                foreach (var member in members)
                {
                    _columns.Add(new ColumnExpression(member.PropertyType,
                        Expression.MakeMemberAccess(obj, member), Alias, member.Name, index++));
                }
            }
    
            return _columns;
        }
    }
    
    private List<ColumnExpression> _columns;

    当然这里有很多可以优化的地方,但是现在先让我们看看使用上述代码后生成的结果

    TableExpression.Columns示例

    注意SelectAlias我赋了空,因为别名需要另外一些逻辑才能确定,不能单纯使用表名。

    这里每个ColumnExpression的Value都是一条MemberExpression,这样我们就得到了详细的元数据了。

    三.数据库表达式格式化器(QueryFormatter)

    扯了这么多,终于可以写点有用的了,我们需要对TableExpression再Visit一遍,这次的是要生成SQL语句了。

    首先为了职责分明,让我们再引入一个格式化器~

    /// <summary>
    /// 查询语句格式化器
    /// </summary>
    public class QueryFormatter : DbExpressionVisitor
    {
    }

    内部放置一个StringBuilder来拼接语句

    private readonly StringBuilder _sb = new StringBuilder();

    重载一下VisitTable

    public override Expression VisitTable(TableExpression table)
    {
        _sb.Append("(SELECT ");
        
      int index = 0;
        foreach (var column in table.Columns)
        {
            if (index++ > 0) _sb.Append(", ");
            this.VisitColumn(column);
        }
        _sb.AppendFormat(" FROM [{0}]", table.Name);
      if (!table.Alias.IsNullOrEmpty())
            _sb.AppendFormat(" As [{0}] ", table.Alias);
    
        _sb.Append(")");
    
        return table;
    }

    再重载一下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;
            default:
                this.Visit(column.Value);
                _sb.AppendFormat(" As [{0}]", column.ColumnName);
                break;
        }
    
        return column;
    }

    只要将TableExpression传递给QueryFormatter.Visit(),QueryFormatter内部的_sb对象就拥有了完整的SQL语句,让我们开放一个方法给外部调用验证下结果先

    public string Format(Expression expression)
    {
        _sb.Clear();
        this.VisitQuery((QueryExpression)expression);
        return this._sb.ToString();
    }
    
    public override Expression VisitQuery(QueryExpression exp)
    {
        switch (exp.DbExpressionType)
        {
            case DbExpressionType.Select: this.VisitSelect((SelectExpression)exp); break;
            case DbExpressionType.Table: this.VisitTable((TableExpression)exp); break;
            case DbExpressionType.Join: this.VisitSource((JoinExpression)exp); break;
        }
    
        return exp;
    }

    这里假设Expression已经被转换为QueryExpression,毕竟没有转换的话我们这个格式化器是无法运转的。

    调用示例如下

    TableExpression解析结果

    团长我完成任务了!( •̀ ω •́ )y

    四.下一步做什么

    我还没想好,等我对代码稍作整理~

  • 相关阅读:
    hdu 1263 水题一道
    hdu 1115 多边形重心
    hdu 4054 字符串处理
    梦想与实验:向阿里巴巴学习
    美国研究员开发电脑芯片模拟人脑工作
    阿里巴巴网站运营模式
    中文Alexa排名专业术语解释
    欧美两大3G标准正式成为我国行业标准
    网站优化的十大奇招妙技
    有志赚钱创业者 从这八个步骤开始
  • 原文地址:https://www.cnblogs.com/MigCoder/p/3722488.html
Copyright © 2020-2023  润新知