前言

相信大家对Entity Framework一定不陌生,我相信其中Linq To Sql是其最大的亮点之一,但是我们一直使用到现在却不曾明白内部是如何实现的,今天我们就简单的介绍IQueryable和IQueryProvider。

IQueryable接口

我们先聊聊这个接口,因为我们在使用EF中经常看到linq to sql语句的返回类型是IQueryable,我们可以看下这个接口的结构:

1 public interface IQueryable : IEnumerable
2 {
3       Type ElementType { get; }
4       Expression Expression { get; }
5       IQueryProvider Provider { get; }
6 }

或许会有人很奇怪,当我们在开发过程中使用这个接口的时候,提供的方法远远不止这么点,因为微软提供了强大的Queryable类,当然大家不要以为这个类是实现IQueryable然后实现了很多方法,如果是那样那些第三方库怎么自定义呢?所以Queryable只是一个静态类,对IQueryable接口进行了扩展,下面是笔者在.Net Reflector截图中一部分:

如果读者细心一点会发现linq to sql并不会导致实际的查询,只有当我们真正开始使用的时候才从数据库中开始查询数据。

IQueryProvider接口

如果我们调试的EF的话,会看到生成的T-SQL语句。T-SQL就是根据表达式树分析从而得出的,而核心就是IQueryProvider接口,下面就是该接口的结构:

1 public interface IQueryProvider
2 {
3         IQueryable CreateQuery(Expression expression);
4         IQueryable<TElement> CreateQuery<TElement>(Expression expression);
5         object Execute(Expression expression);
6         TResult Execute<TResult>(Expression expression);
7 }

其中CreateQuery就是负责解析表达式树的,当然还要将处理后的结果返回,以便接着分析下面的语句,当然这中间只是分析,你完全可以根据表达式树得出你自己需要的查询语句,比如SQL或者其他什么,只有在真正使用数据的时候才会调用Execute方法,这个时候就可以根据我们自己分析的语句开始进行实际的查询了。

实例分析

QueryProvider类

光说不练我们永远不能明白其中的原理,所以下面我们就简单的举一个例子来展示下。首先我们先实现IQueryProvider接口,其中会用到一个Query类,这个类会在后面进行介绍,首先我们新建一个QueryProvider类实现IQueryProvider接口,首先我们看下CreateQuery<S>方法:

这里的expression就是传递给我们,并且需要我们处理的表达式树,最后还要返回实现IQueryable<S>接口的示例,以便LINQ在此基础上进行下面的查询,这里我们仅仅只是创建了一个Query的实例,同时将expression传递给它,因为此处仅仅只是一个DEMO,所以我们没有去真正解析表达式树(这其中要做的工作很多)。接着还有CreateQuery方法:

我们可以看到下面这句话:

实际的含义就是创建Query<>的实例,并且泛型参数是elementType,参数是thisexpression

最后就是Execute方法了,传递一个Expression参数,并获取最后的结果,笔者在这里直接是写死的值:

Query类

仅仅只有QueryProvider还没用,我们还需要一个能够保存表达式树状态的类,当然也包括了我们解析表达式后的结果也可以保存在其中,这样我们在IQueryProvider的Execute方法中就可以根据我们解析的结果执行执行并返回结果了。

这里我们可以看到Query的Expression值在创建这个实例时,如果没有传递Expression参数时该值就是:

但是在后面的过程中Query中的Expression将是QueryProvider中的expression值。

到此我们其实就完成了一个简单的示例了,我们就可以开始测试我们的成果了,笔者在利用如下的代码来测试:

OK,我们开始看看是如何分析这句LINQ语句的。

首先我们看下在一开始执行时Query中Expression的返回值(如下图):

在获取到这个表达式后,就开始执行Linq,首先执行的是where item == 123。

分析Where item == 123

接着我们F5,就可以看到在QueryProvider中的CreateQuery<S>命中了,并且Expression参数如下图所示:

我们看到里面的字符串是 Where(item => (item == 123)),通过这句话我们就可以明白其实LINQ中的where实质上就是利用Where方法,并传递给它对应的lambda表达式。分析完了where部分,下面就是FirstOrDefault部分了。

分析FirstOrDefault

当执行到FirstOrDefault的时候我们可以查看t的值,会发现t实际上就是QueryProvider中CreateQuery<S>的返回值。

接着我们开始执行下面FirstOrDefault方法,发现会再一次的去获取Expression的值,而此时Expression的值就是上面CreateQuery<T>传递给我们的参数expression

然后在将这个表达式树和由表达式树表示FirstOrDefault方法调用的值拼接起来,并调用QueryProvider中的Execute<S>方法,我们可以看到这个时候传递给我们的参数expression的值。

至此一个简单的流程就结束了,最后就是返回笔者写死的123这个值了。

通过上面这个例子我们基本了解了其工作的流程,下面我们将一步一步的分析我们这个where item == 123,当然我们将会用到递归,所以请大家整理好自己的思路,一步一步的看如何从一个表达式树中分析这条语句。

分析表达式树实战

首先我们一个分析表达式树的方法,这个方法我们暂且放在QueryProvider中:

 1 public void AnalysisExpression(Expression exp)
 2         {
 3             switch (exp.NodeType)
 4             {
 5                 case ExpressionType.Call:
 6                     {
 7                         MethodCallExpression mce = exp as MethodCallExpression;
 8                         Console.WriteLine("The Method Is {0}", mce.Method.Name);
 9                         for (int i = 0; i < mce.Arguments.Count; i++)
10                         {
11                             AnalysisExpression(mce.Arguments[i]);
12                         }
13                     }
14                     break;
15                 case ExpressionType.Quote:
16                     {
17                         UnaryExpression ue = exp as UnaryExpression;
18                         AnalysisExpression(ue.Operand);
19                     }
20                     break;
21                 case ExpressionType.Lambda:
22                     {
23                         LambdaExpression le = exp as LambdaExpression;
24                         AnalysisExpression(le.Body);
25                     }
26                     break;
27                 case ExpressionType.Equal:
28                     {
29                         BinaryExpression be = exp as BinaryExpression;
30                         Console.WriteLine("The Method Is {0}", exp.NodeType.ToString());
31                         AnalysisExpression(be.Left);
32                         AnalysisExpression(be.Right);
33                     }
34                     break;
35                 case ExpressionType.Constant:
36                     {
37                         ConstantExpression ce = exp as ConstantExpression;
38                         Console.WriteLine("The Value Type Is {0}", ce.Value.ToString());
39                     }
40                     break;
41                 case ExpressionType.Parameter:
42                     {
43                         ParameterExpression pe = exp as ParameterExpression;
44                         Console.WriteLine("The Parameter Is {0}", pe.Name);
45                     }
46                     break;
47                 default:
48                     {
49                         Console.Write("UnKnow");
50                     }
51                     break;
52             }
53         }

并在CreateQuery<S>中调用这个方法

然后我们可以开始运行测试了,为了能够让读者明白当前处理的表达式树,所以在下面的截图中将会包含AnalysisExpression中参数exp的值,这样可以便于读者区分当前处理的表达式树。

PS:Expression类型中的NodeType是非常重要的,因为传递给我们的都是父类Expression类型,而我们需要根据NodeType的转换成对应的子类,这样我们才能够获取到更详细的信息。

ExpressionType.Call

我们根据一开始的exp的NodeType进入到这个分支,因为where实质上就是ss调用where方法,所以我们通过将exp转换成对应的MethodCallExpression类型,这样我们就可以看到调用的方法名称了。

当然调用一个方法必须要有参数,所以下面还需要循环Arguments去分析具体的参数,其中也包括调用这个方法的对象,自然我们首先是分析调用这个方法的对象,这里我们进行了第一次的递归调用,跳到了ExpressionType.Constant。

ExpressionType.Constant

NodeType为这个类型,我们就可以通过ConstantExpression类型来获取对应的参数,通过Value我们可以可以获取到调用where方法的对象,当然到这里就不会继续往下分析了。

所以我们继续跳到之前的for循环,开始分析第二个参数,就是 item => item == 123这个部分了。

ExpressionType.Quote

如果接触过lambda的人可能会认为类型应该是Lambda,但实际上不会直接跳转到那,而是先跳转到Quote,然后我们再把转换成UnaryExpression类型,然后再继续分析其中Operand属性,而这个属性的NodeType就是Lambda了。个人认为这个应该是区分lambda和普通的方法,因为where不仅仅可以接收lambda同时也可以是常规的方法,所以这里还需要这一层。

ExpressionType.Lambda

跳转到这,大家就不会感觉奇怪了,这里为了简洁。笔者并没有分析参数,而是直接分析Body部分,因为这部分才是我们的关键。

ExpressionType.Equal

我们看到这个lambda很简单,就是一个相等比较,所以直接跳转到了Equal,当然还有And、Or等对应的枚举,而到了这一步我们就可以直接分析Left和Right,当然这里还有一个小插曲,就是在跳到这个枚举的时候我查看exp的类型时,实际上是LogicalBinaryExpression类型,并不是BinaryExpression类型,然后用Reflector查看了下,我就呵呵了。

我当时还奇怪,怎么没有这个类型呢,最后才知道玩的是这一出。到此为止,我们继续分析这个相等操作的左右两边的参数吧。

首先分析的是左边参数item。

ExpressionType.Parameter

Item挑传到这,并将其转换成ParameterExpression类型,笔者在此仅仅只输出了参数的名称。

到这左边的参数分析完毕,我们开始分析右边的参数。

ExpressionType.Constant

我们可以轻松的想到对应的Value就是123了,到此整个表达式就分析完毕了。

我们看看最后控制台的输出结果吧。

在此笔者还要声明一个问题,就是我们应该去理解我们使用的各种库的原理,这样便于我们以后添加符合实际开发的一些功能,当然这并不是浪费时间。而是提高今后项目开发的时间,随着不断的积累,我们会发现很多重复的功能并不需要我们去重复写了,而节省下来的时间我们就可以做自己想做的事了,所以我们要做一个有思想的懒程序员。

源码下载

IQueryable和IQueryProvider初尝相关推荐

  1. 初尝微信小程序2-基本框架

    基本框架: .wxml :页面骨架 .wxss :页面样式 .js :页面逻辑    描述一些行为 .json :页面配置 创建一个小程序之后,app.js,app.json,app.wxss是必须的 ...

  2. 初尝微信小程序2-Swiper组件、导航栏标题配置

    swiper 滑块视图容器. 很多网页的首页都会有一个滚动的图片模块,比如天猫超市首页,滚动着很多优惠活动的图片,用来介绍优惠内容,以及供用户点击快速跳转到相应页面. Swiper不仅可以滚动图片,也 ...

  3. 初尝Java动态代理

    Title: 初尝Java动态代理 Date: 2018-11-22 12:30 Category: 技术博客 Modified: 2018-11-22 12:30 Tags: 动态代理 Slug: ...

  4. HTTP协议,之入门初尝

    在学习HTTP协议的时候,看了一些文章,感觉都很晦涩难懂,尝试图解一下,方便更好的理解记忆.现在先简单记录一篇入门粗浅的博客,更深入的学习,待后续学习跟进再更新. 1.HTTP协议是什么? HTTP- ...

  5. 写给过去的自己-No.2-数据结构篇-初尝柔性数组

    过去你的自己,你好.     照顾宝宝,写完第一篇就没什么时间,既然上次讲的就是数据结构,这次也讲点相关的.     其实接触柔性数组也是个比较奇妙的过程,你以后会遇到个学长,毕业后从事软件行业,在中 ...

  6. Silverlight WCF 初尝小结

    一年多的时间一直在等待Silverlight的稳定版本,但是从1.0 2.0 到现在的3.0BETA 一直吸引着我,于是终于按奈不住,一窥了Silverlight的魅力. Silverlight是什么 ...

  7. BluePrism初尝2

    在接近三周的自学中,初步体验到了RPA的甜头. 在对BP这个工具慢慢的深入接触中,从0 到1的探索式学习,从最开始的一个个的小功能模块的用途,每一个的属性的功能,到现在自己能初步尝试组织一些简单的流程 ...

  8. .NET Conf 2017后初尝Xamarin Forms 3.0@Linux

    对很多.NET粉,.NET Conf 2017的东西估计提前一个月都熟悉了,Xamarin粉估计最大惊喜不是Xamarin Live Player, 也不是Xamarin.Forms混合NativeC ...

  9. Xamarin for iOS 11(一) - 初尝ARKit

    编者语:Xamarin 的兼容性是它最大的优点,对于iOS / Android 新的功能支持也是无缝的,做到100%兼容.Xamarin.ios for iOS 11.0的支持已经开始,大家可以在这里 ...

最新文章

  1. 1803无法升级到2004_Win10再度误伤“友军”:升级五月更新后OneDrive同步报错
  2. 《×××颂》贵在突破了中国花鸟画难以反映社会主题的尴尬
  3. mysql创建字段非空NOT NULL的好处
  4. (二)Docker配置修改阿里云镜像仓库
  5. 算法提高 日期计算c语言,算法提高 日期计算
  6. win7域内桌面黑屏
  7. php怎么排除空的数组,【技术产品】php如何去除空数组
  8. 计算机网络 HTTP工作机制 TCP三次握手四次挥手 TCP滑动窗口
  9. luoguP4705 玩游戏
  10. 封装和使用Docker流程
  11. 多种方法破解Windows 系统密码
  12. 京东羚珑页面可视化平台介绍
  13. OpenCV4学习笔记(59)——高动态范围(HDR)成像
  14. 调制深度(modulation depth)是什么?
  15. 树莓派2 是否值得购买、入手?
  16. Java操作Word图表
  17. Dcloud产品HbuilderX、uniapp你用过吗
  18. 算法小结 之 蛮力法
  19. wr703n 官方固件140120版本刷openwrt
  20. android 群控 网络 adb,安卓群控.sln · 罗金方/结合mini和adb命令,和adb socket实现安卓群控 - Gitee.com...

热门文章

  1. pycharm安装scrapy失败_运行Scrapy程序时出现No module named win32api问题的解决思路和方法...
  2. python定义私有变量的方法_Python怎么修改私有属性 如何访问python类中的私有方法...
  3. uniapp动态设置style和class样式
  4. uniapp中radio颜色渐变
  5. CSS基础学习-7.CSS元素分类
  6. java中如何实现两个值互换
  7. Mac OS build caffe2 Error:This file was generated by an older version of protoc which is
  8. web在线聊天框滚动条自动在底部
  9. android之启动桌面activity
  10. Internet Explorer 8 使用技巧(2):加速器