前言

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

IQueryable接口

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

 public interface IQueryable : IEnumerable
{
Type ElementType { get; }
Expression Expression { get; }
IQueryProvider Provider { get; }
}

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

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

IQueryProvider接口

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

 public interface IQueryProvider
{
IQueryable CreateQuery(Expression expression);
IQueryable<TElement> CreateQuery<TElement>(Expression expression);
object Execute(Expression expression);
TResult Execute<TResult>(Expression expression);
}

其中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中:

 public void AnalysisExpression(Expression exp)
{
switch (exp.NodeType)
{
case ExpressionType.Call:
{
MethodCallExpression mce = exp as MethodCallExpression;
Console.WriteLine("The Method Is {0}", mce.Method.Name);
for (int i = ; i < mce.Arguments.Count; i++)
{
AnalysisExpression(mce.Arguments[i]);
}
}
break;
case ExpressionType.Quote:
{
UnaryExpression ue = exp as UnaryExpression;
AnalysisExpression(ue.Operand);
}
break;
case ExpressionType.Lambda:
{
LambdaExpression le = exp as LambdaExpression;
AnalysisExpression(le.Body);
}
break;
case ExpressionType.Equal:
{
BinaryExpression be = exp as BinaryExpression;
Console.WriteLine("The Method Is {0}", exp.NodeType.ToString());
AnalysisExpression(be.Left);
AnalysisExpression(be.Right);
}
break;
case ExpressionType.Constant:
{
ConstantExpression ce = exp as ConstantExpression;
Console.WriteLine("The Value Type Is {0}", ce.Value.ToString());
}
break;
case ExpressionType.Parameter:
{
ParameterExpression pe = exp as ParameterExpression;
Console.WriteLine("The Parameter Is {0}", pe.Name);
}
break;
default:
{
Console.Write("UnKnow");
}
break;
}
}

并在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. 初尝Windows 下批处理编程

    本文叫“ 初尝Windows 下批处理编程”是为了延续上一篇“初尝 Perl”,其实对于博主而言批处理以及批处理编程早就接触过了. 本文包括以下内容 1.什么是批处理 2.常用批处理命令 3.简介批处 ...

  2. 初尝 Perl

    本文将阐述以下几方面内容: 1.什么是Perl 2.Perl有什么用 3.Windows 下的Perl环境搭建 4.Perl 版Hello World 5.Perl 语法梗概 6.一些参考资料 什么是 ...

  3. seajs初尝 加载jquery返回null解决学习日志含示例下载

    原文地址:http://www.tuicool.com/articles/bmuaEb 如需demo示例,请点击下方链接下载: http://yunpan.cn/cVEybKs8nV7CF  提取码 ...

  4. .NET领域驱动设计—初尝(三:穿过迷雾走向光明)

    开篇介绍 在开始这篇富有某种奇妙感觉的文章之旅时我们先短暂的讨论一下关于软件开发方法论的简要: 纵观软件开发方法论,从瀑布模型.螺旋模型.RUP(统一软件开发过程).XP(极限编程).Agile(敏捷 ...

  5. 认识IQueryable和IQueryProvider接口

    1.Func<Student, bool>和Expression<Func<Student, bool>>的区别 class Program { static vo ...

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

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

  7. 分布式设计《初尝memcached》

          之前听说过高性能的分布式缓存开源工具,但一直没有真正接触过,如今接触的产品中实用到过分布式缓存.所以决定一探到底.memcached是一个优秀的开源的分布式缓存工具.也是眼下比較火热的分布 ...

  8. .NET领域驱动设计—初尝(一:疑问、模式、原则、工具、过程、框架、实践)

     .NET领域驱动设计—初尝(一:疑问.模式.原则.工具.过程.框架.实践) 2013-04-07 17:35:27 标签:.NET DDD 驱动设计 原创作品,允许转载,转载时请务必以超链接形式标明 ...

  9. Github原生CI/CD,初尝Github Actions

    Github 原生 CI/CD,初尝 Github Actions Intro Github 目前已经推出了自己的 CICD 服务 -- Github Actions,而且比微软的 Azure Dev ...

随机推荐

  1. Egret 压缩与解压(jszip)

    一 jszip是什么 二 为什么要用jszip 三 如果使用zip 一 jszip是什么 jszip 是一个 JavaScript 库,可直接在浏览器上创建 zip 压缩档. 二 为什么要用jszip ...

  2. Devexpress VCL Build v2014 vol 15.2.3 发布

    2016年第一个版本,继续修补. New Major Features in 15.2 What's New in VCL Products 15.2 Breaking Changes To lear ...

  3. 2016.02.14 总结JS事件

    今天主要总结JS事件的基本知识以及使用技巧,并作出相应的DEMO.

  4. Android 下拉刷新框架实现

    原文地址:http://blog.csdn.net/leehong2005/article/details/12567757 前段时间项目中用到了下拉刷新功能,之前在网上也找到过类似的demo,但这些 ...

  5. 错误:Method not found: 'Void System.Web.UI.ScriptResourceDefinition.set_LoadSucce

    vs2012开发,再把.net 4.5降成4.0.部署到2003 服务器上就报错了. 在网上查了下,老外说:这个方法.net4.0不支持. 后面发现是发布方式错了,用项目里的发布,不要直接拷贝文件过去 ...

  6. FIFO跨时钟域读写

    今天面试,要走时问了我一个问题:如果两个时钟一个时钟慢一个时钟快,来读写FIFO,其中读出的数据是 连续的一段一段的. 图1 图1为写时序控制,可以看出数据是两个时钟周期的长度,当然实际中可以是任意周 ...

  7. bootstrap的日期插件datetimepicker有问题

    bootstrap的日期插件datetimepicker在chrome中会出现掉下来的现象,而且一直没找到原因,下载最新版的插件直接在各个浏览器中都会掉下来, 问题一直解决不了,转而换其他插件 htt ...

  8. ListView和ScrollView冲突

    当ListView放在ScrollView中的时候,无论你设置高度为 match_parent(填充父窗体)和wrap_content(包裹内容)都只显示一行,这是你把ListView放在Linear ...

  9. shell脚本学习(一)

    Shell脚本最常用于系统管理工作,或者用于结合现有的程序以完成小型.特定的工作. Shell的特点有: 1. 简单性 2. 可移植性 3. 开发容易 [什么是shell] 简单点理解,就是系统跟计算 ...

  10. GNU M4 - GNU Project - 免费软件基金会(FSF)

    -------------------------------------------------------------------------------------- GNU M4介绍: GNU ...