深入剖析IEnumerable和IQueryable两接口

前言

日常开发过程中,经常会遇到IEnumerableIQueryable这两个接口,也许大家也能熟练的运用这两个接口对数据库或者集合进行各种复杂查询。

实际上如果错误的使用,会导致很多很多系统优化,查询效率等等问题。以及很多人吐槽EntityFramework效率低下。实际上很大原因就是IEnumerableIQueryable使用的问题!

那么,它们究竟是如何定义的,都分别用来干什么的?又尤其是IQueryable,它和EntityFramework的延迟加载技术又有什么联系呢?

定义

  • Enumerable类,继承自IEnumerable<T>接口的集合进行扩展
  • Queryable类,继承自IQueryable<T>接口的集合进行扩展

既然说到EntityFramework,那么就要看一下EntityFramework的实体集合DbSet<T>

从定义上看,DbSet<T>也同时实现了IEnumerable<T>IQueryable<T>。也就意味着DbSet<T>通过实现IEnumerable<T>IQueryable<T>的基础上,可以拥有EnumerableQueryable这两个静态类的很多方法的扩展。

从扩展方法上分辨区别

### Enumerable: ### Queryable:

上面两张图可以看出 EnumerableQueryable下的扩展方法第二个参数是分别是Func<T>Expression<Func<T>>

这就有疑问了,难道我们平时用的Where()等扩展方法,原来有时候都不是同一个方法么?

其实我们反编译仔细观察IQueryable的话,实际上也继承了IEnumerable,所以这两个接口的方法,在很大程度上是一样的,那么,微软为什么要设计出两套扩展方法呢?

好,下面继续深入研究一下。

为了方便大家更直观的理解,接下来就用代码驱动了,透过现象看本质。

我们先创个控制台项目,把EntityFramework包引入,顺便建一个测试用的MSSQL数据库,以及实体和数据库上下文

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
using System;
using System.Linq;

namespace ConsoleApp9
{
class Program
{
static void Main(string[] args)
{
using (var db = new testEntities())
{
var query = db.Set<User>().Where(e => e.UserName == "小红");

//var query = db.Set<User>().AsEnumerable().Where(e => e.UserName == "小红");

foreach (var item in query)
{
Console.WriteLine(item.UserName);
}
}

Console.ReadKey();
}
}
}

首先注意下query变量的类型

可以看到query是个IQueryable<User>,也就是说,DbSet<T>Where()方法,默认是来自Queryable下的扩展方法

我们都知道所有的ORM框架,包括EntityFramework,都是将代码转换成SQL语句来对数据库进行操作。所以我们就要从生成的SQL语句来对比IEnumerableIQueryable

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