• LinqToDB 源码分析——生成与执行SQL语句


    生成SQL语句的功能可以算是LinqToDB框架的最后一步。从上一章中我们可以知道处理完表达式树之后,相关生成SQL信息会被保存在一个叫SelectQuery类的实例。有了这个实例我们就可以生成对应的SQL语句。想要了解这一步部分的功能就必须从三个方面入手。一、Linq To SQL的机制原理。二、如何生成SQL语句。三、设置映射结果。

    生成映射表达式


    对于Linq To SQL的机制原理在前面的章节里面已经讲过了。这里笔者提出来主要目标是明确什么时候触发。下面的代码不是看前面的获得Query<T>类实列,而是看后面的GetIEnumerable方法调用。

     ExpressionQuery<T>类:

    IEnumerable<T> Execute(IDataContextInfo dataContextInfo, Expression expression)
    {
        return GetQuery(expression, true).GetIEnumerable(null, dataContextInfo, expression, Parameters);
    }

    记得笔者前面几个章节中讲到最后都会去调用俩个方法分别是Query<T>类中的GetIEnumerable方法和GetElement方法。而这俩个方法都是Func类型。如下

    public Func<QueryContext, IDataContextInfo, Expression, object[], object> GetElement;
    public Func<QueryContext, IDataContextInfo, Expression, object[], IEnumerable<T>> GetIEnumerable;

    显然很明显在调用GetIEnumerable方法一定要知道哪一个方法赋给他了。好了,先暂停一下。让我们去看一下上一章中笔者讲到Build<T>()方法有三个重要方法中的一个——BuildQuery()方法。

     1 internal Query<T> Build<T>()
     2 {
     3      var sequence = BuildSequence(new BuildInfo((IBuildContext)null, Expression, new SelectQuery()));
     4 
     5       if (_reorder)
     6            lock (_sync)
     7            {
     8                _reorder = false;
     9                _sequenceBuilders = _sequenceBuilders.OrderByDescending(_ => _.BuildCounter).ToList();
    10            }
    11 
    12        _query.Init(sequence, CurrentSqlParameters);
    13 
    14        var param = Expression.Parameter(typeof(Query<T>), "info");
    15 
    16        sequence.BuildQuery((Query<T>)_query, param);
    17 
    18        return (Query<T>)_query;
    19 }

    事实在调用GetIEnumerable方法之前,上面的BuildQuery()方法里面已经对GetIEnumerable进行了赋值一个新的方法。对于BuildQuery()方法只要点击进去看的话,就会发现他并不是属于XxxxBuilder类的。而是属于XxxxBuilder类对应的IBuildContext接口实例。例如下面

    TableContext类:

     public void BuildQuery<T>(Query<T> query, ParameterExpression queryParameter)
    {
        var expr = BuildQuery(typeof(T), this, null);
        var mapper = Builder.BuildMapper<T>(expr);
    
        query.SetQuery(mapper);
    }

    好像没有发现对于GetIEnumerable进行赋值的代码。不要紧张我们先看一下这代码是做什么的。假设我们已经生成SQL语句,也执行了数据库了。那么得到数据库的结果又是什么样子映射成对象类呢?看样子大家一定明白笔者的意思。没有错。这边就是设置回返结果的映射。同样子作者也是用表达式来构建一个方法来设置返回对象结果。具体做法读者可以自己断点进去看。mapper就是最后生成的映射表达式树。我们可以看到他做为参数传给了SetQuery()方法。

    Query<T>类:

    internal void SetQuery(Expression<Func<QueryContext, IDataContext, IDataReader, Expression, object[], T>> expression)
    {
        var query = GetQuery();
        var mapInfo = new MapInfo { Expression = expression };
    
        ClearParameters();
    
        GetIEnumerable = (ctx, db, expr, ps) => Map(query(db, expr, ps, 0), ctx, db, expr, ps, mapInfo);
    }

    好,看到这一段代码。我们可以看到他构建一个MapInfo类。记得笔者前面章节的图片有出现过。最后数据库的结果就是通过MapInfo类转化成相关的对象结果。而这边我们还可以看到GetIEnumerable被重新赋值了。为什么说是被重新赋值了。因为在Query<T>类的构造函数里就已经对GetIEnumerable赋值过了。读者们可以在去查看一下。

    这个时候我们就是能明白调用GetIEnumerable方法,事实上是在调用上面代码中的赋值的Map方法。所以很明显去看Map方法做什么就是明白如何调用作者构建方法。即是上面提到的表达式构建的方法。

    生成SQL语句


    最后一定要执行数据库,这一步操作离不开上面讲到的GetIEnumerable方法。同时我们也知道GetIEnumerable方法事实上是在调用Map方法。而这个过程中会用到一个叫PreparedQuery类。这个类就是用于执行数据库的预查询类。里面存放了生成的SQL语句。

    Query<T>类:

     1  TE RunQuery<TE>(
     2             QueryContext ctx,
     3             IDataContextInfo dataContextInfo,
     4             Expression expr,
     5             object[] parameters,
     6             Func<QueryContext, IDataContext, IDataReader, Expression, object[], TE> mapper)
     7 {
     8             var dataContext = dataContextInfo.DataContext;
     9 
    10             object query = null;
    11 
    12             try
    13             {
    14                 query = SetCommand(dataContext, expr, parameters, 0, true);
    15 
    16                 using (var dr = dataContext.ExecuteReader(query))
    17                     while (dr.Read())
    18                         return mapper(ctx, dataContext, dr, expr, parameters);
    19 
    20                 return Array<TE>.Empty.First();
    21             }
    22             finally
    23             {
    24                 if (query != null)
    25                     dataContext.ReleaseQuery(query);
    26 
    27                 if (dataContextInfo.DisposeContext)
    28                     dataContext.Dispose();
    29             }
    30 }

    上面这段代码是执行数据库的入口地方。笔者用红色标出了执行数据库之前,所做事情的相关代码——生成SQL语句。不过我们会发现query不是一个字符串,而是PreparedQuery类实例。那么我们看一下生成PreparedQuery的地方,就能明白生成SQL语句离不开一个叫BasicSqlBuilder类。

    DataConnection类:

     1 internal PreparedQuery GetCommand(IQueryContext query)
     2 {
     3      if (query.Context != null)
     4      {
     5           return new PreparedQuery
     6           {
     7                     Commands = (string[])query.Context,
     8                     SqlParameters = query.SelectQuery.Parameters,
     9                     SelectQuery = query.SelectQuery,
    10                     QueryHints = query.QueryHints,
    11           };
    12      }
    13 
    14      var sql = query.SelectQuery.ProcessParameters();
    15      var newSql = ProcessQuery(sql);
    16 
    17      if (!object.ReferenceEquals(sql, newSql))
    18      {
    19           sql = newSql;
    20          sql.IsParameterDependent = true;
    21      }
    22 
    23      var sqlProvider = DataProvider.CreateSqlBuilder();
    24 
    25      var cc = sqlProvider.CommandCount(sql);
    26      var sb = new StringBuilder();
    27 
    28      var commands = new string[cc];
    29 
    30      for (var i = 0; i < cc; i++)
    31      {
    32           sb.Length = 0;
    33 
    34           sqlProvider.BuildSql(i, sql, sb);
    35           commands[i] = sb.ToString();
    36      }
    37 
    38      if (!query.SelectQuery.IsParameterDependent)
    39                 query.Context = commands;
    40 
    41       return new PreparedQuery
    42       {
    43                 Commands = commands,
    44                 SqlParameters = sql.Parameters,
    45                 SelectQuery = sql,
    46                 SqlProvider = sqlProvider,
    47                 QueryHints = query.QueryHints,
    48        };
    49 }

    上面红色部分就是生成SQL语句相关的代码问部分。对于BasicSqlBuilder类笔者简单的做一些介绍。做一个初步的了解。想要更深入的了解。最好自己去查看一下代码。BasicSqlBuilder类根据DML来进行划分的。所以我们可以看到下列方法

    1.BuildSelectQuery方法:构建查询语句。
    2.BuildDeleteQuery方法:构建删除语句。
    3.BuildUpdateQuery方法:构建更新语句。
    4.BuildInsertQuery方法:构建插入语句。
    等等

    同时又依据SQL语句的结果分为以下方法。

    1.BuildSelectClause:关键字SELECT部分的语句。
    2.BuildFromClause:关键字FROM部分的语句。
    3.BuildWhereClause:关键字WHERE部分的语句。
    4.BuildGroupByClause:关键字GROUPBY部分的语句。
    等等

    BasicSqlBuilder类事实上笔者认为比较简单。而且笔者的目地都是在引导一种查看源码的思路。想要从源码中学习到东西还是要靠自己去分析才行。

    结语句


    本系列的文章笔者也只能引导到这里了。笔者对本系列的定位就是帮助想要了解LinqToDB框架的人做一个引导和分析思路的工作。正如上面讲的想要从源码中学习到东西还是要靠自己去分析才行。

  • 相关阅读:
    如何挖掘需求,覆盖整个系统
    JVM全整理
    7.linux文件与目录管理
    6.linux的文件权限与目录配置
    获取外汇基本汇率
    Pointer-Events: 如何处理ScreenTouch和MouseClicks
    Excel如何快速定位出两列的不同值
    Java数据结构: java.util.BitSet源码学习
    一道面试题与Java位操作 和 BitSet 库的使用
    Test post.
  • 原文地址:https://www.cnblogs.com/hayasi/p/6111385.html
Copyright © 2020-2023  润新知