前言

相信大家对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. 8.12 CSS知识点5

    背景原点 background-origin 设置元素背景图片的原始起始位置,必须保证背景是background-repeat为no-repeat此属性才会生效. 语法: background-ori ...

  2. linux(debian)下邮件发送

    关键字: exim4  mutt  smtp 主要的事情就是配置exim4,按照网上的流程来.在这里总结一下: 需要修改的文件有三个:/etc/exim4/update-exim4.conf.conf ...

  3. Ajax readystate 5种状态

    Status 说明 0(Uninitialized) XMLHttpRequest 对象已经创建,但没调用 open 方法. 1(Loading) 调用 open 方法,但没调用 send 方法.(尚 ...

  4. Warchall: Live RCE

    具体漏洞是:CVE-2012-1823(PHP-CGI RCE) 在地址后面加进参数运行对应的php-cgi 参数的行为 例如 index.php?-s 相参于/usr/bin/php53-cgi/p ...

  5. urllib.request

    [urllib.request] 1.urlopen结果保存在内存. 2.ulrretrieve结果保存到文件. 3.response有read方法. 4.可以创建Request对象. 5.发送Pos ...

  6. ueditor 发布到服务器提示“后端配置项没有正常加载,上传插件不能正常使用!”

    原来是发布后缺少文件,直接从本地的复制过去就好了.

  7. Android WebView 实现缓存网页数据

    WebView中存在着两种缓存:网页数据缓存(存储打开过的页面及资源),H5缓存(即AppCache). 将我们浏览过的网页url已经网页文件(css.图片.js等)保存到数据库表中 缓存模式(5种) ...

  8. mybatis(二)接口编程 、动态sql 、批量删除 、动态更新、连表查询

    原理等不在赘述,这里主要通过代码展现. 在mybatis(一)基础上,新建一个dao包,并在里面编写接口,然后再在xml文件中引入接口路径,其他不变,在运用阶段将比原始方法更节约时间,因为不用再去手动 ...

  9. 使用Aspose插件将程序中的表格,导出生成excel表格

    http://www.cnblogs.com/lanyue52011/p/3372452.html这个是原文地址 /// <summary> /// 点击按钮,将内存表导出excel表格! ...

  10. 使用spring 4.0 + maven 构建超简单的web项目

    一.需求 使用spring去管理web项目,是目前非常流行的一种思路,本文将介绍使用maven+spring 4.0.2 来构建一个简单的web项目. 二.实现 1.新建一个maven项目,如下图所示 ...