配色: 字号:
LinqToDB 源码分析——轻谈Linq查询
2016-11-09 | 阅:  转:  |  分享 
  
LinqToDB源码分析——轻谈Linq查询

LinqToDB框架最大的优势应该是实现了对Linq的支持。如果少了这一个功能相信他在使用上的快感会少了一个层次。本来笔者想要直接讲解LinqToDB框架是如何实现对Linq的支持。写到一半的时候却发现本系列在内容上的引导显得格外的生硬。思考在三最后还是决定在讲解LinqToDB框架之前来一章过度文。



Linq查询的原理

我们在学习Linq的时候会见到一些很常见的关键词语。比如LinqToSQL、LinqToObjects、LinqToXML等。事实这些一般都是根据不同的数据源来进行命名的。说实话笔者当初学习的时候,看到这些命名险些以为只有这几种。事实不是这样子的。Linq有俩个核心类——Enumerable类和Queryable类。这俩个类可以说贯穿整个Linq知识体系。如果有心的朋友可以点开对应的dll包就是发现他们都在System.Linq命名空间下。同时他们都是用于扩展相应的静态方法。而且方法名大至相同。然后他们却在本质上有着细微的差别。Enumerable类是对IEnumerable接口进行扩展并且传入了Func类型的参数。数据源是来自于内存中的。而Queryable类是对IQueryable接口进行扩展,传入参数是表达式(Expression类型)。数据源是来自于第三方。比如SQLServer、MySql等。



Linq的思想就是提供一个统一模型操作来处理数据。所以本质来讲对数据源不是很讲究。比如数据源是文件,或则说数据源是Excel之类的。相信可能有人已经看到过LinqToExcel呢?主要辛苦还是这些开发底层的人。对于使用者来讲没有什么多大的差别。Linq现在面对数据源而扩展功能有很多。其中专对数据库来讲,最流行还是有LinqToSQL。而且扩展数据库的Linq功能大多数都用IQueryable接口。当然,这不是说用IEnumerable接口就不行了。只是这俩种接口在实现上有着很大的差别。IEnumerable接口我们都知道他一般是专对于内存的。这意味着我们必须把相应的数据全部加载到内存中才可以进行查询。这样子的操作太伤性能了。而IQueryable接口我们可以巧妙的用上表达式树(ExpressionTree)进行转化生成对应的数据库SQL语句,然后在执行数据库。这才是显得合理。



ORM思想能流行大体上可以说是因为他的思想更加贴切于人类的思维方式。在笔者看来如果把Linq技术说成也是ORM思想的产物之一,这样子的说法也不为过。这也是笔者喜欢Linq的地方。LinqToDB框架只所以都能支持Linq。不可否认也是依据这一种上面所讲的原理来实现的。大体的想法如下。



实现Linq提供的IQueryable接口和IQueryProvider接口。生成相关的表达式树。

把对应的表达式树转化生成对应数据库的SQL语句。并执行。

根据映射的信息,生成对应的集合类。(这里的集合类是指SQL语句执行结果转成类放入的集合)

实现自定义的Linq查询一定离不开俩类——IQueryable接口和IQueryProvider接口。上面的工作可以说都在这俩类上面。IQueryable接口一般用于生成对应的表达式树。而IQueryProvider接口用于执行表达式树,转化成对应的SQL语句,执行数据库并生成映射的模型对象。



注意:IQueryable接口是什么样子生成相关的表达式树呢?让笔者来讲的话,笔者觉得有一点浪费时间。但是不要当心博客园里面有一位大神写的博文一定能满足你——王清培的《.NET深入解析LINQ框架》。



实现Linq查询

支持Linq查询本意上就是实现上面所讲的俩个接口。当你实现IQueryable接口的时候,VS提示你实现三个属性。如果你F12进去查看他有些什么内容的话,你会发现什么也没有。为了方便笔者还是把他贴出来了。



IQueryable接口:



publicinterfaceIQueryable:IEnumerable,IQueryable,IEnumerable

{

}

IQueryable接口:



publicinterfaceIQueryable:IEnumerable

{

TypeElementType{get;}

ExpressionExpression{get;}

IQueryProviderProvider{get;}

}

这些属性全部来自于IQueryable接口。同时你还会发现——我去!他即然继承了IEnumerable接口。笔者不惊的感叹微软真会玩。ElementType属性的作用也正如他的名字一样子——元素类型。用于指定当前要查询的是什么类型的数据。对于Expression属性的话,也正如上面笔者讲到IQueryable接口会为我们建立一个表达式树。Expression属性便是用于建立表达式树的。最后一个Provider属性。他是IQueryProvider类型的。用于执行表达式生成对应的数据库SQL语句和执行数据库。



IQueryProvider接口有四个方法。事实上应该说是俩个才对。因为他们是俩俩功能相同。IQueryable接口生成完表达式树之后,最终执行的有俩个方法一个是来IEnumerable的GetEnumerator方法,一个是来IQueryProvider接口的Execute方法。那么笔者上面讲到的“执行表达式生成对应的数据库SQL语句和执行数据库”的功能也是在这俩个方法中实现的。



好了。笔者说在多也没有什么用。不如笔者写一个小小的应用来说明这一切。如下



复制代码

1publicclassAomiQuery:IOrderedQueryable

2{

3publicAomiQuery()

4{

5this.Expression=System.Linq.Expressions.Expression.Constant(this);

6this.Provider=newAomiQueryProvider();

7}

8publicAomiQuery(Expressionexpression,IQueryProviderprovider)

9{

10this.Expression=expression;

11this.Provider=provider;

12}

13publicIEnumeratorGetEnumerator()

14{

15return(Provider.Execute>(Expression)).GetEnumerator();

16}

17

18System.Collections.IEnumeratorSystem.Collections.IEnumerable.GetEnumerator()

19{

20return(Provider.Execute(Expression)).GetEnumerator();

21}

22

23publicTypeElementType

24{

25get{returntypeof(T);}

26}

27

28publicExpressionExpression{privateset;get;}

29

30publicIQueryProviderProvider{privateset;get;}

31}

复制代码

上面这段代码中,笔者用是不是IQueryable接口而是IOrderedQueryable接口。事实上不有多大的差别。IOrderedQueryable接口是继承IQueryable接口。官方的说法是IQueryable接口不能实现Orderby功能。IOrderedQueryable接口却可以。当然关于这一点,有兴趣的读者们可以自行去看看。



IQueryable接口实现完了。让我们在实现一下IQueryProvider接口吧。



复制代码

1publicclassAomiQueryProvider:IQueryProvider

2{

3

4publicIQueryableCreateQuery(Expressionexpression)

5{

6returnnewAomiQuery(expression,this);

7}

8

9publicIQueryableCreateQuery(System.Linq.Expressions.Expressionexpression)

10{

11TypeelementType=TypeSystem.GetElementType(expression.Type);

12try

13{

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

15}

16catch(System.Reflection.TargetInvocationExceptiontie)

17{

18throwtie.InnerException;

19}

20}

21

22publicTResultExecute(Expressionexpression)

23{

24boolIsEnumerable=(typeof(TResult).Name=="IEnumerable`1");

25return(TResult)this.ExecuteReader(expression,IsEnumerable);

26}

27

28publicobjectExecute(Expressionexpression)

29{

30returnthis.ExecuteReader(expression);

31}

32

33publicobjectExecuteReader(Expressionexpression,boolisEnumerable=false)

34{

35if(expressionisMethodCallExpression)

36{

37MethodCallExpressionmce=expressionasMethodCallExpression;

38SqlConnectionconnection=newSqlConnection("DataSource=.;InitialCatalog=Northwind;PersistSecurityInfo=True;UserID=sa;Password=123");

39SqlCommandcommand=newSqlCommand();

40command.Connection=connection;

41

42StringBuildercommandText=newStringBuilder();

43

44if(mce!=null&&mce.Method.DeclaringType==typeof(Queryable)&&mce.Method.Name=="Where")

45{

46commandText.Append("SELECTFROM");

47

48ConstantExpressionce=mce.Arguments[0]asConstantExpression;

49IQueryablequeryable=ce.ValueasIQueryable;

50

51commandText.Append(queryable.ElementType.Name);

52commandText.Append("WHERE");

53

54UnaryExpressionue=mce.Arguments[1]asUnaryExpression;

55LambdaExpressionlambda=ue.OperandasLambdaExpression;

56BinaryExpressionbe=lambda.www.wang027.comBodyasBinaryExpression;

57MemberExpressionlme=be.LeftasMemberExpression;

58ConstantExpressionrce=be.RightasConstantExpression;

59

60commandText.Append(lme.Member.Name);

61

62switch(be.NodeType)

63{

64caseExpressionType.And:

65commandText.Append("AND");

66break;

67caseExpressionType.Or:

68commandText.Append("OR");

69break;

70caseExpressionType.Equal:

71commandText.Append("=");

72break;

73caseExpressionType.NotEqual:

74commandText.Append("<>");

75break;

76caseExpressionType.LessThan:

77commandText.Append("<");

78break;

79caseExpressionType.LessThanOrEqual:

80commandText.Append("<=");

81break;

82caseExpressionType.GreaterThan:

83commandText.Append(">");

84break;

85caseExpressionType.GreaterThanOrEqual:

86commandText.Append(">=");

87break;

88}

89

90commandText.Append(rce.Value);

91

92}

93

94

95command.CommandText=commandText.ToString();

96

97ListproList=newList();

98

99connection.Open();

100

101SqlDataReaderdr=command.ExecuteReader();

102

103while(dr.Read())

104{

105Productsproduct=newProducts();

106product.ProductID=Convert.ToInt32(dr["ProductID"]);

107product.ProductName=Convert.ToString(dr["ProductName"]);

108proList.Add(product);

109}

110

111dr.Close();

112connection.Close();

113

114returnisEnumerable?proList.AsEnumerable():proList;

115

116}

117

118returnnull;

119}

复制代码

在笔者看来大量的工作都放在了IQueryProvider接口上面,IQueryable接口显得更加像一个帮手——就是帮我们建表达式树。IQueryProvider接口里面有俩方法叫CreateQuery。从代码中我们就可以看到他会返回一个新的IQueryable接口实例。由于笔者这边只是写一个where功能。如果有orderby功能的话,那么CreateQuery方法会被在调用一次。同时传入where过程中生成的表达式。当然这个时候我们还要在新建一个IQueryable接口实例。注意每一次新建IQueryable接口实例都会把上一个的表达式传入到实例的Expression属性。具体的情况,我觉得读者们可以自己做一个小试验来看看会比较好。如果你看过王大神的博文的话,相信这个一定不在话下。上面红色的代码是笔者处理表达式树的。在正式开发过程中往往是不可能这样子做的。笔者这边只是想要表达一个意思。在执行数据库之前,一定要先转化成对应的SQL。



执行代码:



复制代码

staticvoidMain(string[]args)

{

AomiQueryaomiProducts=newAomiQuery();

varquery=frompinaomiProductswherep.ProductID>30selectp;

ListproList=query.ToList();



foreach(ProductspinproList)

{

Console.WriteLine("ProductID:{0}---------------->ProductName:{1}",p.ProductID,p.ProductName);

}



Console.ReadKey();

}

复制代码

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