深入剖析IEnumerable和IQueryable两接口
前言
日常开发过程中,经常会遇到IEnumerable
和IQueryable
这两个接口,也许大家也能熟练的运用这两个接口对数据库或者集合进行各种复杂查询。
实际上如果错误的使用,会导致很多很多系统优化,查询效率等等问题。以及很多人吐槽EntityFramework
效率低下。实际上很大原因就是IEnumerable
和IQueryable
使用的问题!
那么,它们究竟是如何定义的,都分别用来干什么的?又尤其是IQueryable
,它和EntityFramework的延迟加载技术又有什么联系呢?
定义
Enumerable
类,继承自IEnumerable<T>
接口的集合进行扩展Queryable
类,继承自IQueryable<T>
接口的集合进行扩展
既然说到EntityFramework,那么就要看一下EntityFramework的实体集合DbSet<T>

从定义上看,DbSet<T>
也同时实现了IEnumerable<T>
和IQueryable<T>
。也就意味着DbSet<T>
通过实现IEnumerable<T>
和IQueryable<T>
的基础上,可以拥有Enumerable
和Queryable
这两个静态类的很多方法的扩展。
从扩展方法上分辨区别
### Enumerable
: ###
Queryable
:
上面两张图可以看出 Enumerable
和Queryable
下的扩展方法第二个参数是分别是Func<T>
和Expression<Func<T>>
这就有疑问了,难道我们平时用的Where()
等扩展方法,原来有时候都不是同一个方法么?
其实我们反编译仔细观察IQueryable
的话,实际上也继承了IEnumerable
,所以这两个接口的方法,在很大程度上是一样的,那么,微软为什么要设计出两套扩展方法呢?
好,下面继续深入研究一下。
为了方便大家更直观的理解,接下来就用代码驱动了,透过现象看本质。
我们先创个控制台项目,把EntityFramework包引入,顺便建一个测试用的MSSQL数据库,以及实体和数据库上下文
1 | using System; |
首先注意下query
变量的类型
可以看到query
是个IQueryable<User>
,也就是说,DbSet<T>
的Where()
方法,默认是来自Queryable
下的扩展方法
我们都知道所有的ORM框架,包括EntityFramework,都是将代码转换成SQL语句来对数据库进行操作。所以我们就要从生成的SQL语句来对比IEnumerable
和IQueryable
IEnumerable
:

IQueryable
:

对比生成的SQL语句,我们发现,IEnumerable进行筛选的时候,没有where条件!
也就是说,IEnumerable会将数据库里所有的数据全部取过来,然后再在内存里进行查询!
结论
1.对于IEnumerable
的过滤、排序、分组、聚合等操作,都是在内存中进行的。
2.对于IQueryable
的过滤、排序、分组、聚合等操作,Linq to SQL引擎会把表达式树转化成相应的SQL语句在数据库中执行,这也是Linq的延迟加载核心思想所在。
其实IQueryable
实现了IEnumerable
接口。但多了几个属性
看到这里,想必就能解释为什么IQueryable
,会比IEnumerable
传递委托时,会多套上一个Expression
答案就在于:Expression
会把查询表达式生成表达式树缓存起来,只有当真正需要用到的时候,才会由IQueryProvider
解析表达式树,翻译成sql语句执行数据库查询操作。而Func是个委托,必须要先执行完才能进行下一个方法的调用。
更直白点的说,就是:IQueryable
是负责生成SQL语句的,但并不马上执行;而IEnumerable
是对任意类型的集合都能操作。
引出的代码规范
- 操作本地数据源时用IEnumerable
- 操作远程数据源时用IQueryable