IQueryable 和 IEnumerable的区别

这是 EF Core 系列的最后一篇文章,按照上一篇的计划,我们最后就讲一讲 IQueryable 和 IEnumerable 的区别。

在前面的一些例子中,我们会在使用 LINQ 查询方法之后,又使用 ToList 等方法,将查询结果转换成集合。

如果我们不使用 ToList 呢?.

比如这个示例:

using var context = new BloggingContext();
var posts = context.Posts.OrderBy(p => p.BlogId == 1).ToList();
var posts1 = context.Posts.OrderBy(p => p.BlogId == 1);

这里有两个查询,一个使用了 ToList 方法,另一个没有。

执行这段代码,在控制台中查看日志:

IQueryable 和 IEnumerable的区别

 

可以看到只有一条 SQL 语句被执行,按理说我们有两个 LINQ 查询,应该有两条 SQL 语句,但是只有一条,这是为什么?

 

其实,我们调用的 LINQ 查询方法,本身是不会执行查询操作的。

简单来说,我们执行的 LINQ 方法,只是转换成了查询表达式,被存储在了 IQueryable 类型的对象中。

只有当我们需要数据的时候,比如当我们使用 ToList 方法时,EF Core 才会将整个出啊讯表达式,翻译成 SQL 语句执行。

这种需要数据时才会查询的行为,看起来有点像显式加载,但绝对不是一回事儿。

结合示例简单的说,「post1」 只是一个以 LINQ 表达式体现的查询语句,就好比你编写了一条字符串 SQL 语句。

至于这条语句是否执行,取决于下一步操作。比如 ToList 方法,就是这一步查询操作。

因为使用 ToList 方法的目的,就是为了获取数据集合,所以 EF Core 才会执行这条查询语句。

如果非要给这种行为起一个名字的话,我觉得应该叫延迟执行更合适。

我们再来看看,LINQ 方法返回的数据类型是什么:

IQueryable 和 IEnumerable的区别

 

「post1」 的数据类型是泛型的 IOrderedQueryable,它本质上是就是一个 IQueryable 类型,不过是具有排序表达式的 IQueryable

 

LINQ 方法往往都有好几个重载,比如 OrdeyByWhere 等方法,它们返回的数据类型有两种:IQueryable 和 IEnumerable

那么这两种数据类型究竟有何区别?我们做个试验:

IQueryable 和 IEnumerable的区别

 

在这个示例中,「posts」 和 「posts1」 的语句,可以视为等效的。

 

这是因为查询参数会被默认为 IQueryable 类型,所以最终使用的还是返回 IQueryable 类型的重载方法。

在 post2 的语句中,由于 IQueryable 继承了 IEnumerable,所以可以通过 AsEnumerable 方法,将其返回值转换为 IEnumerable 类型。

在这种情况下,只有当代码运行到两个 foreach 遍历的时候,才会真正的执行语句。

接下来,再看这个示例:

IQueryable 和 IEnumerable的区别

 

这个示例与前面一个示例的不同之处在于,「posts1」 查询语句的 LINQ 方法在 AsEnumerable 之后才调用。

 

运行程序,观察控制台日志:

IQueryable 和 IEnumerable的区别

 

此时我们终于发现了不同,两条 SQL 语句不同,第一条 SQL 语句比第二条 SQL 语句多了一个分页操作。

 

IQueryable 在查询的时候,会应用所有 LINQ 方法,并且生成一个完整的 SQL 查询语句,去数据库执行并获取符合查询语句的数据;

而当我们使用 IEnumerable 时,则会生成一条将所有的数据查询出来的 SQL 语句,而后在内存中再对数据进行处理,最终获得符合查询语句的数据。

在不使用 AsEnumerable 的情况下,默认都是采用 IQueryable 进行查询。

那么我们什么时候应该使用 AsEnumerable ?

使用 「AsEnumerable」 的查询,称为客户端查询。

比如,当你想在客户端完成筛选或者聚合等操作的时候,可以选择转换成 IEnumerable 进行查询。

虽然这增大了数据库查询压力和IO压力,但因为将计算转移到了客户端,所以也相应的减轻了数据库的计算压力。

简单来说是,这是一种用 I/O 资源换计算资源的行为。