配色: 字号:
LinqToDB 源码分析——生成表达式树
2017-01-21 | 阅:  转:  |  分享 
  
LinqToDB源码分析——生成表达式树



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



IQueryable接口和IQueryProvider接口

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



publicinterfaceIExpressionQuery:IOrderedQueryable,IQueryProvider

{

newExpressionExpression{get;set;}

stringSqlText{get;}

}

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



varquery=frompindbContext.Productswherep.ProductID==30selectp;

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



protectedvoidInit(IDataContextInfodataContextInfo,Expressionexpression)

{

DataContextInfo=dataContextInfo??newDefaultDataContextInfo();

Expression=expression??Expression.Constant(this);

}

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



泛型CreateQuery方法:



IQueryableIQueryProvider.CreateQuery(Expressionexpression)

{

if(expression==null)

thrownewArgumentNullException("expressionreturnnewExpressionQueryImpl(DataContextInfo,expression);

}

普通CreateQuery方法:



复制代码

IQueryableIQueryProvider.CreateQuery(Expressionexpression)

{

if(expression==null)

thrownewArgumentNullException("expression");



varelementType=expression.Type.GetItemType()??expression.Type;



try

{

return(IQueryable)Activator.CreateInstance(typeof(ExpressionQueryImpl<>).MakeGenericType(elementType),newobject[]{DataContextInfo,expression});

}

catch(TargetInvocationExceptionex)

{

throwex.InnerException;

}

}

复制代码

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



Query类

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



复制代码

1abstractclassExpressionQuery:IExpressionQuery

2{

3[NotNull]

4publicExpressionExpression{get;set;}

5[NotNull]

6publicIDataContextInfoDataContextInfo{get;set;}

7

8internalQueryInfo;

9internalobject[]Parameters;

10}

复制代码

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



ExpressionQuery类的Execute方法:



TResultIQueryProvider.Execute(Expressionexpression)

{

return(TResult)GetQuery(expression,false).GetElement(null,DataContextInfo,expression,Parameters);

}

ExpressionQuery类的GetEnumerator方法:



IEnumeratorIEnumerable.GetEnumerator()

{

IEnumerableresult=Execute(DataContextInfo,Expression);

IEnumeratoriEnumerator=result.GetEnumerator();

returniEnumerator;

}

GetEnumerator方法里面的Execute方法:



IEnumerableExecute(IDataContextInfodataContextInfo,Expressionexpression)

{

returnGetQuery(expression,true).GetIEnumerable(null,dataContextInfo,expression,Parameters);

}

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



复制代码

classQuery:Query

{

//......

//......

//......

publicFuncGetElement;

publicFunc>GetIEnumerable;

//......

//......

//......



}

复制代码

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



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



ExpressionQuery类的GetQuery方法:



复制代码

QueryGetQuery(Expressionexpression,boolcache)

{

if(cache&&Info!=null)

returnInfo;



varinfo=Query.GetQuery(DataContextInfo,expression);



if(cache)

Info=info;



returninfo;

}

复制代码

Query的静态方法GetQuery:



复制代码

1publicstaticQueryGetQuery(IDataContextInfodataContextInfo,Expressionexpr)

2{

3varquery=FindQuery(dataContextInfo,expr);

4

5if(query==null)

6{

7lock(_sync)

8{

9query=FindQuery(dataContextInfo,expr);

10

11if(query==null)

12{

13if(Configuration.Linq.GenerateExpressionTest)

14{

15vartestFile=newExpressionTestGenerator().GenerateSource(expr);

16DataConnection.www.tt951.comWriteTraceLine(

17"Expressiontestcodegenerated:''"+testFile+"''.",

18DataConnection.TraceSwitch.DisplayName);

19}

20

21try

22{

23query=newExpressionBuilder(newQuery(),dataContextInfo,expr,null).Build();

24}

25catch(Exception)

26{

27if(!Configuration.Linq.GenerateExpressionTest)

28{

29DataConnection.WriteTraceLine(

30"Togeneratetestcodetodiagnosetheproblemset''LinqToDB.Common.Configuration.Linq.GenerateExpressionTest=true''.",

31DataConnection.TraceSwitch.DisplayName);

32}

33

34throw;

35}

36

37if(!query.DoNotChache)

38{

39query.Next=_first;

40_first=query;

41}

42}

43}

44}

45

46returnquery;

47}

复制代码

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



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



复制代码

staticvoidMain(string[]args)

{

using(AdoContextdbContext=newAdoContext())

{

varquery=frompindbContext.Productswherep.ProductID==30selectp;

ListcatalogsList=query.ToList();

ListcatalogsList1=query.ToList();

}

}

复制代码

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



复制代码

staticvoidMain(string[]args)

{

using(AdoContextdbContext=newAdoContext())

{

varquery=frompindbContext.Productswherep.ProductID==30selectp;

varquery1=frompindbContext.Productswherep.ProductID==30selectp;

varquery2=frompindbContext.Productswherep.ProductName=="Aomi"selectp;

varquery3=frompindbContext.Productswherep.ProductID==30selectp;

Listcatalowww.baiyuewang.netgsList=query.ToList();

ListcatalogsList11=query.ToList();

ListcatalogsList1=query1.ToList();

ListcatalogsList2=query2.ToList();

ListcatalogsList3=query3.ToList();

}

}

献花(0)
+1
(本文系thedust79首藏)