zoukankan      html  css  js  c++  java
  • LinqToDB 源码分析——生成表达式树

    当我们知道了Linq查询要用到的数据库信息之后。接下就是生成对应的表达式树。在前面的章节里面笔者就已经介绍过。生成表达式树是事实离不开IQueryable<T>接口。而处理表达式树离不开IQueryProvider接口。LinqToDB框架跟这俩个接口有关系的有三个类:Table<T>类、ExpressionQuery<T>类、ExpressionQueryImpl<T>类。其中最重要的是ExpressionQuery<T>类。他是Table<T>和ExpressionQueryImpl<T>类的父类。而本章就是围绕这三个类进行的。

    IQueryable<T>接口和IQueryProvider接口


     ExpressionQuery<T>类是一个抽象类。他实现于IExpressionQuery接口。IExpressionQuery接口却同时继续了IQueryable<T>接口和IQueryProvider接口。所以ExpressionQuery<T>类实际上同时实现了俩个接口。而同时自己增加一个属性SqlText和一个跟IQueryable接口一样子的Expression属性。SqlText属性是用于获得当前表达式对应的SQL语句。这个属性显然在开发过程非常有用。而Expression属性是用于生成表达式树的。

    public interface IExpressionQuery<out T> : IOrderedQueryable<T>, IQueryProvider
    {
        new Expression Expression { get; set; }
        string         SqlText    { get; }
    }

    从前面的章节里面,我们可以知道实现IQueryable<T>接口和IQueryProvider接口的类有俩个部分的职责。一是帮助生成表达式树,二是用于Linq To SQL的数据源的入口。作者也相应的设计了俩个类。就是上面讲到的Table<T>和ExpressionQueryImpl<T>类。ExpressionQueryImpl<T>类用于前者,而Table<T>类用于后者。

    var query = from p in dbContext.Products where p.ProductID == 30 select p;

    上面这段Linq查询中的dbContext.Products。事实上是通过IDataContext的静态扩展方法GetTable<T>来实现的。当然事情并没有这么简单。从源码中笔者看到这一个过程还离不开DataContextInfo类。DataContextInfo类是里面存放了关于DataContext类实例的信息。从代码量我们可以知道一个信息——DataContextInfo类必须有。

    protected void Init(IDataContextInfo dataContextInfo, Expression expression)
    {
         DataContextInfo = dataContextInfo ?? new DefaultDataContextInfo();
         Expression = expression ?? Expression.Constant(this);
    }

    我们都知道在生成表达式树的时候,实现俩个CreateQuery方法的重要性。LinqToDB框架也离不开这一点。CreateQuery<TElement>方法直接新建一个ExpressionQueryImpl<TElement>类。而CreateQuery方法用反射来实现(笔者认为这地方只有这里有看点其他的真的没有)。关于ExpressionQueryImpl<TElement>类真的没有什么可说的。笔者也就不提他了。

    泛型CreateQuery方法:

    IQueryable<TElement> IQueryProvider.CreateQuery<TElement>(Expression expression)
    {
         if (expression == null)
                 throw new ArgumentNullException("expressionreturn new ExpressionQueryImpl<TElement>(DataContextInfo, expression);
    }

    普通CreateQuery方法:

    IQueryable IQueryProvider.CreateQuery(Expression expression)
    {
         if (expression == null)
                throw new ArgumentNullException("expression");
    
          var elementType = expression.Type.GetItemType() ?? expression.Type;
    
         try
         {
                    return (IQueryable)Activator.CreateInstance(typeof(ExpressionQueryImpl<>).MakeGenericType(elementType), new object[] { DataContextInfo, expression });
         }
         catch (TargetInvocationException ex)
         {
                    throw ex.InnerException;
         }
    }

    从这里面笔者就是感觉出来IQueryable<T>接口更多只是帮助我们生成相应的表达式树。当然这里面离不开IQueryProvider接口的CreateQuery方法的帮忙。正因为CreateQuery方法我们可以知道每一个表达树节点都会新建一个对象。这个对象就是ExpressionQueryImpl<TElement>类。而这个对象里面的Expression属性就是当前节点的表达式。所以每新建一个对象都会把expression参数传入。

    Query类


     Linq查询的难点就是处理表达式。上面笔者粗略的讲了一些生成表达式。主要是因为前面章节中已经讲过这一部分的内容。而且LinqToDB框架关于这一点又没有做出什么特色的设计。但是关于处理表达式树的设计内容笔者感觉还有可以学习和介见的。所有处理表达树的类都在LinqToDB.Linq.Builder命名空间下。更是以XxxxBuilder的命名规则来新建相应的类。但是在讲处理表达树之前,笔者还是先讲一下关于Query类的内容。事实上从ExpressionQuery<T>的性属中我们就可以找到Query类的影子。

     1 abstract class ExpressionQuery<T> : IExpressionQuery<T>
     2 { 
     3         [NotNull]
     4         public Expression Expression { get; set; }
     5         [NotNull]
     6         public IDataContextInfo DataContextInfo { get; set; }
     7 
     8         internal Query<T> Info;
     9         internal object[] Parameters;
    10 }    

    那么Query类到底是什么角色呢?又有什么作用呢?从《LinqToDB 源码分析——轻谈Linq查询》章节中我们能知道最后执行的方法有俩个Execute方法和GetEnumerator方法。所以Query类对应也有俩个方法跟他们相应。

    ExpressionQuery类的Execute方法:

     TResult IQueryProvider.Execute<TResult>(Expression expression)
     {
        return (TResult)GetQuery(expression, false).GetElement(null, DataContextInfo, expression, Parameters);
     }

    ExpressionQuery类的GetEnumerator方法:

    IEnumerator<T> IEnumerable<T>.GetEnumerator()
    {
         IEnumerable<T> result = Execute(DataContextInfo, Expression);
         IEnumerator<T> iEnumerator = result.GetEnumerator();
         return iEnumerator;
    }

    GetEnumerator方法里面的Execute方法:

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

    从源码中我们可以看到Query类的GetElement方法和GetIEnumerable方法各自对应一个最终执行方法。GetElement方法对应ExpressionQuery类的Execute方法,而GetIEnumerable方法对应ExpressionQuery类的GetEnumerator方法。不过,GetElement方法和GetIEnumerable方法都是Func类型。

    class Query<T> : Query
        {
            //......
           //......
           //......
            public Func<QueryContext, IDataContextInfo, Expression, object[], object> GetElement;
            public Func<QueryContext, IDataContextInfo, Expression, object[], IEnumerable<T>> GetIEnumerable;
           //......
           //......
           //......
    
    }

    从这里我们就能感觉到一点那就是好像最后执行会在Query类里面做。笔者只能说没有错。事实上关于Query类现在笔者也很难去形容他。这个类设计到底是一个什么样子的存在。相信也只有作者才能搞清楚。总之从代码上来看的话,他有一点像是保妈一样子。负责各个类之间的引导工作。

    处理表达式树的工作并不是由Query类来做的。而由一个叫ExpressionBuilder类来做的(下一章会讲到)。但是Query类最后会负责接受ExpressionBuilder类产生的结果。Query类会拿着结果去找DataContext类来处理执行生成数据库结果。然后Query类在拿数据库结果去找MapInfo类生成对应的最终结果。看看。像不像保妈一样子。关于Query类的具体内容还是要接合ExpressionBuilder类才能介绍清楚。笔者后面会讲到。现在主要让我们看一下Query类是什么样子由来的。ExpressionQuery<T>类中有一个方法GetQuery方法。上面讲到的最后执行的方法(Execute方法和GetEnumerator方法)都会去调用他。Query类又调用自己本身的静态方法GetQuery。一切也就是从这里开始的。

    ExpressionQuery<T>类的GetQuery方法:

    Query<T> GetQuery(Expression expression, bool cache)
    {
        if (cache && Info != null)
               return Info;
    
        var info = Query<T>.GetQuery(DataContextInfo, expression);
    
        if (cache)
              Info = info;
    
        return info;
    }

    Query的静态方法GetQuery:

     1  public static Query<T> GetQuery(IDataContextInfo dataContextInfo, Expression expr)
     2         {
     3             var query = FindQuery(dataContextInfo, expr);
     4 
     5             if (query == null)
     6             {
     7                 lock (_sync)
     8                 {
     9                     query = FindQuery(dataContextInfo, expr);
    10 
    11                     if (query == null)
    12                     {
    13                         if (Configuration.Linq.GenerateExpressionTest)
    14                         {
    15                             var testFile = new ExpressionTestGenerator().GenerateSource(expr);
    16                             DataConnection.WriteTraceLine(
    17                                 "Expression test code generated: '" + testFile + "'.",
    18                                 DataConnection.TraceSwitch.DisplayName);
    19                         }
    20 
    21                         try
    22                         {
    23                             query = new ExpressionBuilder(new Query<T>(), dataContextInfo, expr, null).Build<T>();
    24                         }
    25                         catch (Exception)
    26                         {
    27                             if (!Configuration.Linq.GenerateExpressionTest)
    28                             {
    29                                 DataConnection.WriteTraceLine(
    30                                     "To generate test code to diagnose the problem set 'LinqToDB.Common.Configuration.Linq.GenerateExpressionTest = true'.",
    31                                     DataConnection.TraceSwitch.DisplayName);
    32                             }
    33 
    34                             throw;
    35                         }
    36 
    37                         if (!query.DoNotChache)
    38                         {
    39                             query.Next = _first;
    40                             _first = query;
    41                         }
    42                     }
    43                 }
    44             }
    45 
    46             return query;
    47         }

    我们可以明显的看到想要获得Query类,就必须通过ExpressionBuilder类来获得。这里的内容就多了。而除了这一点之外,作者也为Query类做了小缓存。ExpressionQuery<T>的性属Info是为了Query类本身的缓存。而FindQuery方法是为了所有的Query类缓存的。

    1.Query类本身的缓存。如下,第一次用query变量的时候要加载实例化Query类。第二次在用query变量的时候就不必了。

    static void Main(string[] args)
    {
         using (AdoContext dbContext = new AdoContext())
         {
              var query = from p in dbContext.Products where p.ProductID == 30 select p;
              List<Products> catalogsList = query.ToList();
              List<Products> catalogsList1 = query.ToList();
        }
    }

    2.所有的Query类缓存的。下面中query和query1、query3是一样子的。所以query1,query2是不会在加载实例Query类的。FindQuery方法用的缓存方式有一点像链接队列——只是笔者忘记了专业的名称。如果链接缓存中哪一个query被用最后会放到链接的最前面。如果在链接缓存中没有找到,就接在最后面。

    static void Main(string[] args)
    {
          using (AdoContext dbContext = new AdoContext())
          {
                    var query = from p in dbContext.Products where p.ProductID == 30 select p;
                    var query1 = from p in dbContext.Products where p.ProductID == 30 select p;
                    var query2 = from p in dbContext.Products where p.ProductName == "Aomi" select p;
                    var query3 = from p in dbContext.Products where p.ProductID == 30 select p;
                    List<Products> catalogsList = query.ToList();
                    List<Products> catalogsList11 = query.ToList();
                    List<Products> catalogsList1 = query1.ToList();
                    List<Products> catalogsList2 = query2.ToList();
                    List<Products> catalogsList3 = query3.ToList();
           }
    }

    LinqToDB框架是轻量级的ORM框架。所以笔者也很喜欢作者这样子设计缓存——简单而又实用。

    好了。有于工作的原因。笔者本单只能介绍到这里了。

  • 相关阅读:
    升级windows 11小工具
    windows 10更新升级方法
    您需要了解的有关 Oracle 数据库修补的所有信息
    Step by Step Apply Rolling PSU Patch In Oracle Database 12c RAC Environment
    Upgrade Oracle Database Manually from 12.2.0.1 to 19c
    如何应用版本更新 12.2.0.1.210420(补丁 32507738 – 2021 年 4 月 RU)
    xtrabackup 安装、备份和恢复
    Centos_Lvm expand capacity without restarting CentOS
    Centos_Lvm_Create pv vg lv and mount
    通过全备+relaylog同步恢复被drop的库或表
  • 原文地址:https://www.cnblogs.com/hayasi/p/6071665.html
Copyright © 2011-2022 走看看