【转载】IQueryable 和 IEnumerable 的区别
IQueryable 和 IEnumerable 的区别 #
不管是Linq to object,还是Linq to sql或Linq to Entity,IQueryable
和IEnumerable
都是延迟执行的,它们之间的区别仅仅在于扩展方法的参数类型不同。(迭代/枚举方式不同?作用对象不同?)
IQueryable 和 IEnumerable 的区别#
-
ToList()
:立即执行。会立即执行sql,取出数据到内存中。 -
AsEnumerable()
:延迟执行,真正使用时才执行sql读取数据。此处有坑,一定要往下看
对IQueryable
对象使用AsEnumerable()
后,仍然是延迟执行,不过此时对象本质已经变了。
前面已经说了 IEnumerable
的扩展方法接受的是Func
(C#语法),当ie对象(iq转变) 真正使用时,会有2个步骤:
- 它会把iq对象(转变之前的) 的扩展方法翻译成sql语句,查询出数据加载到内存中,变为ie对象;
- 此时再把ie对象(转变之后的) 的扩展方法,使用C#求解,得到最终结果。
例如:
iq对象的Skip、Take方法,会被翻译成sql,在数据库里执行取出最终结果。
而ie对象的Skip、Take方法,则会取出全部数据到内存中,在内存中执行Skip、Take,会耗费大量资源。
使用场景#
异常:System.InvalidOperationException: 无法枚举查询结果多次#
在Linq to sql或EF(非EFCore)中,直接执行sql语句来查询数据后,对数据集(IEnumerable
)进行多次枚举操作就会引发这个异常。
经测试,多次Count()
会引发此异常。其他的Sum()
、foreach
等等应该也是,有待验证。。。
我的理解是:数据集( 搞不懂枚举器和迭代器了,需要研究下。。。IEnumerable
)是使用枚举器来处理每项数据的,而枚举器只能走一次。
注意,EFCore中不会出现这个异常,原因请搜索efcore执行sql
。
解决方法#
下面的代码是Linq to sql的,网上说EF也会出现该异常,代码应该类似。
另外网上搜索该异常大部分都是执行存储过程时出现的,其实也是直接执行sql来查询数据,本质一样。
string sql = @"
select top 100 *
from [dbo].[BaseSupplier_OTAOnline]";
//return db.ExecuteQuery(sql, parameters);
IEnumerable ieBs02 = bdb.QueryBySql(sql);//"exec Pro_BaseSpOtaOnline_Test01"
int count = ieBs02.Count();
ieBs02 = ieBs02.Skip(1).Take(2);
//****此处会引发异常****
int count02 = ieBs02.Count();
误区:对 iq对象 和 ie对象 使用foreach
时,对于循环的每项都要查询数据库#
也可以这样想:如果是一条一条取数据的话,程序怎么知道每次应该取哪条数据?
-
使用
DataReader
?不行,效率太低下。因为取出每条数据后,还需要对数据进行一系列的操作(代码逻辑),这需要耗费时间。而
DataReader
是需要在线保持数据库连接的,耗时太长会导致同一时间有很多数据库连接,很快就会达到数据库连接池上限。这种方法很不可取。 -
对生成的sql进行
top 1
处理?那要怎么知道每次取出哪条数据呢?使用上一条数据的信息作为
where
条件?不行,这么做太傻逼,网络数据传输增加;查询效率也低下;占用数据库连接池资源。种种缺点,简单问题复杂化。
由上面的反例可以看出,一条一条查数据可以实现,但是太二逼。完全不如一次性全部读取数据的好。
其他#
先说下结论:
- 只会把
IQueryable
的条件(Expression
)翻译成sql,IEnumerable
的条件(Func
)不会被翻译成sql。代码中生成的sql可以验证。 - 二者都是延迟执行的,真正使用过的时候才会查询数据库。
NetFramework#
测试环境:
- AspNetCore 2.1
- EFCore
//不查询数据库
IQueryable iqOta = ctx.BaseSupplier_OTAOnline.Where(p => p.ID < 10);
//不查询数据库
IEnumerable ieOta = iqOta.AsEnumerable();
//不查询数据库
ieOta = ieOta.Where(p => p.ID > 5);
//执行sql
//只执行iq的条件
//查询数据库
//SELECT [p].[ID], [p].[AddDate], [p].[AddUser], [p].[EditCode], [p].[EditDate], [p].[GetDate], [p].[Note], [p].[OTAName], [p].[OnlineSupplier], [p].[PushDate], [p].[PushUrl]
//FROM [BaseSupplier_OTAOnline] AS [p]
//WHERE [p].[ID] < 10
List bsList = ieOta.ToList();
//再次查询数据库
//SELECT [p].[ID], [p].[AddDate], [p].[AddUser], [p].[EditCode], [p].[EditDate], [p].[GetDate], [p].[Note], [p].[OTAName], [p].[OnlineSupplier], [p].[PushDate], [p].[PushUrl]
//FROM [BaseSupplier_OTAOnline] AS [p]
//WHERE [p].[ID] < 10
foreach (var item in ieOta)
{
}