IQueryable 和 IEnumerable 的区别
在C#中使用 Linq to sql 时,经常搞混 IQueryable
和 IEnumerable
这两种类型,本文简单分析下它们之间的区别和使用场景。
前言
不管是Linq to object,还是Linq to sql或Linq to Entity,IQueryable
和IEnumerable
都是延迟执行的,它们之间的区别仅仅在于扩展方法的参数类型不同。(迭代/枚举方式不同?作用对象不同?)
IQueryable
和 IEnumerable
的区别
IQueryable
:扩展方法接受的是Expression
对于Linq to sql或Linq to Entity,
Expression
必须要能转成sql
,否则会报错。IEnumerable
:扩展方法接受的是Func
(Func
是C#语法)。IEnumerable
跑的是Linq to Object,会强制从数据库中读取所有数据到内存里,所以可以使用C#语法。对于Linq to sql或Linq to Entity,
Func
是无限制的,因为它是使用C#语法操作数据的。这也从侧面说明:数据已经读取到内存中了。IEnumerable
的扩展方法是Func
,不会转换为sql。转换为sql的是内部的IQueryable
。所以要注意条件最好限制在IQueryable
里,否则IQueryable
可能会读取大量数据,增加耗时和内存。
AsEnumerable()
和 ToList()
的区别
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
,会耗费大量资源。
使用场景
IQueryable
:使用EFCore动态拼接多个where
条件时使用。(延迟查询,每次真正使用时都会重新读取数据。)IEnumerable
:当扩展方法无法转换为sql
时,可以使用AsEnumerable()
转换为IEnumerable
。因为IEnumerable
的扩展方法都是使用C#语法处理数据的。(延迟查询,每次真正使用时都会重新读取数据。)ToList()
:当where
条件已经确定了,就可以使用ToList()
从数据库中立即取出数据,后面重复使用这些数据就行。
不过为了省事,我一般都是使用IQueryable
拼接好条件后,直接ToList()
来使用了。。。
备注
异常:System.InvalidOperationException
: 无法枚举查询结果多次
异常出现条件
在Linq to sql或EF(非EFCore)中,直接执行sql语句来查询数据后,对数据集(IEnumerable
)进行多次枚举操作就会引发这个异常。
经测试,多次Count()
会引发此异常。其他的Sum()
、foreach
等等应该也是,有待验证。。。
我的理解是:数据集(IEnumerable
)是使用枚举器来处理每项数据的,而枚举器只能走一次。 搞不懂枚举器和迭代器了,需要研究下。。。
注意,EFCore中不会出现这个异常,原因请搜索efcore执行sql
。
解决方法
- 方法1:把查询结果
ToList()
,后续使用List
来操作数据。 - 方法2【推荐】: 抛弃内置的,使用Dapper,因为Dapper的查询结果本质是
List
。(多结果集不是,更多信息搜索Dapper
。)
异常重现代码
下面的代码是Linq to sql的,网上说EF也会出现该异常,代码应该类似。
另外网上搜索该异常大部分都是执行存储过程时出现的,其实也是直接执行sql来查询数据,本质一样。
string sql = @"
select top 100 *
from [dbo].[BaseSupplier_OTAOnline]";
//return db.ExecuteQuery<T>(sql, parameters);
IEnumerable<BaseSupplier_OTAOnline> ieBs02 = bdb.QueryBySql<BaseSupplier_OTAOnline>(sql);//"exec Pro_BaseSpOtaOnline_Test01"
int count = ieBs02.Count();
ieBs02 = ieBs02.Skip(1).Take(2);
//****此处会引发异常****
int count02 = ieBs02.Count();
误区:对 iq对象 和 ie对象 使用foreach
时,对于循环的每项都要查询数据库
错误!
foreach
针对的是数据集整体对象(迭代器?)。当使用foreach
时,不管是iq对象还是ie对象,它们都是查询数据库一次,然后开始循环,直至循环结束。不过,当后续再次使用iq对象或ie对象的具体数据时,它们仍然会再次查询数据库。
注意:iq对象的结果是数据集。它只能把当前存储的表达式树转换为sql。它无法对其进行处理来做到一次一条的取出数据,因为根本就不可能!怎么能无中生有呢?
反向验证
也可以这样想:如果是一条一条取数据的话,程序怎么知道每次应该取哪条数据?
使用
DataReader
?不行,效率太低下。因为取出每条数据后,还需要对数据进行一系列的操作(代码逻辑),这需要耗费时间。而
DataReader
是需要在线保持数据库连接的,耗时太长会导致同一时间有很多数据库连接,很快就会达到数据库连接池上限。这种方法很不可取。对生成的sql进行
top 1
处理?那要怎么知道每次取出哪条数据呢?使用上一条数据的信息作为
where
条件?不行,这么做太傻逼,网络数据传输增加;查询效率也低下;占用数据库连接池资源。种种缺点,简单问题复杂化。
由上面的反例可以看出,一条一条查数据可以实现,但是太二逼。完全不如一次性全部读取数据的好。
其他
IQueryable
和IEnumerable
生成sql的测试代码
先说下结论:
- 只会把
IQueryable
的条件(Expression
)翻译成sql,IEnumerable
的条件(Func
)不会被翻译成sql。代码中生成的sql可以验证。 - 二者都是延迟执行的,真正使用过的时候才会查询数据库。
NetFramework
测试环境:
- .NET Framework 4.5
- LINQ to SQL类(不是EntityFramework)
BaseSpDB bdb = new BaseSpDB();//不查询数据库IQueryable<BaseSupplier_OTAOnline> iqBs = bdb.baseSpByAll().Where(p => p.ID < 10);//不查询数据库IEnumerable<BaseSupplier_OTAOnline> ieBs = iqBs.AsEnumerable();//不查询数据库ieBs = ieBs.Where(p => p.ID > 5);//执行sql//只执行iq的条件//查询数据库
//exec sp_executesql N'SELECT [t0].[ID], [t0].[OTAName], [t0].[OnlineSupplier], [t0].[PushUrl], [t0].[Note], [t0].[EditCode], [t0].[AddUser], [t0].[PushDate], [t0].[GetDate], [t0].[EditDate], [t0].[AddDate]
//FROM [dbo].[BaseSupplier_OTAOnline] AS [t0]
//WHERE [t0].[ID] < @p0',N'@p0 int',@p0=10List<BaseSupplier_OTAOnline> bsList = ieBs.ToList();//再次查询数据库
//exec sp_executesql N'SELECT [t0].[ID], [t0].[OTAName], [t0].[OnlineSupplier], [t0].[PushUrl], [t0].[Note], [t0].[EditCode], [t0].[AddUser], [t0].[PushDate], [t0].[GetDate], [t0].[EditDate], [t0].[AddDate]
//FROM [dbo].[BaseSupplier_OTAOnline] AS [t0]
//WHERE [t0].[ID] < @p0',N'@p0 int',@p0=10foreach (var item in ieBs){}
NetCore
测试环境:
- AspNetCore 2.1
- EFCore
//不查询数据库IQueryable<BaseSupplier_OTAOnline> iqOta = ctx.BaseSupplier_OTAOnline.Where(p => p.ID < 10);//不查询数据库IEnumerable<BaseSupplier_OTAOnline> 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] < 10List<BaseSupplier_OTAOnline> 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] < 10foreach (var item in ieOta){}
IQueryable 和 IEnumerable 的区别相关推荐
- C# IQueryable 和 IEnumerable 的区别
这是 EF Core 系列的最后一篇文章,按照上一篇的计划,我们最后就讲一讲 IQueryable 和 IEnumerable 的区别. 点击上方或后方蓝字,阅读 EF Core 系列合集. 在前面的 ...
- 【C#】IQueryable和IEnumerable的区别
IEnumerable接口 公开枚举器,该枚举器支持在指定类型的集合上进行简单迭代.也就是说:实现了此接口的object,就可以直接使用foreach遍历此object: IQueryable 接口 ...
- EFCore——IQueryable与IEnumerable的区别(13)
IQueryable与IEnumerable的区别 一.IQueryable与IEnumerable的简单实例 二.IQueryable与IEnumerable的区别 一.IQueryable与IEn ...
- C#中IQueryable和IEnumerable的区别
IQueryable接口是继承自IEnumerable的接口 IQueryable中有表达式树, 这可以看作是它的一个优势.所以,使用IQueryable操作时,比如对数据的过滤,排序等操作, 这些都 ...
- IQueryable和IEnumerable的区别
转载于:https://www.cnblogs.com/myyBlog/p/8961062.html
- IQueryable和IEnumerable,IList的区别
IQueryable和IEnumerable都是延时执行(Deferred Execution)的,而IList是即时执行(Eager Execution) IQueryable和IEnumerabl ...
- EntityFramework中IEnumerable和IQueryable的含义和区别
先说下IList,IList对SQL语句是即时执行的,IEnumerable和IQueryable是延时执行的,用到才执行. IQueryable和IEnumerable在每次执行时都必须连接数据库读 ...
- 对IQueryable和IEnumerable的认识
** IQueryable和IEnumerable的区别 public static IQueryable<TSource> Where<TSource>(this IQuer ...
- IQueryable和IEnumerable区别
IQueryable 和IEnumerable总结 1,IEnumerable<T> result = (from t in context.Table order by t.Id se ...
最新文章
- matlab 自动控制仿真,Matlab在自动控制系统建模与仿真中的应用
- Kinect SDK V1.7 开发工具包概览
- c6011取消对null指针的引用_C++中的野指针及其规避方法
- 金蝶国际公布2020年全年业绩,云业务收入增长45.6%
- linux mysql 5.6.23_mysql 5.6.23 的安装
- 转 mysql处理高并发,防止库存超卖
- java 指针 引用_java中的引用与c中的指针
- C++中的struct与class继承方式
- IT人看《国富论》系列:第一篇之第二章:论分工的原由。分工其实是人类利己倾向的结果...
- gwas snp 和_Science | 群体研究新思路:De novo + GWAS
- Lwip协议详解(基于Lwip 2.1.0)TCP协议 (未完待续)
- 【STM32H7的DSP教程】第13章 DSP快速计算函数-三角函数和平方根
- 【Unity】Unity Shader学习笔记(一)Unity Shader基础
- QQ等App每天自启百次;​李国庆直播拍卖午餐时间1小时;苹果提交认证9款新手机 | EA周报...
- 电子表格软件2013 免费版
- python程序员面试算法宝典pdf-Python程序员面试笔试宝典
- html中常用判断和工具(二)
- IOS中__bridge,__bridge_retained和__bridge_transfer理解
- 蒙特卡洛随机模拟的MATLAB实例解析纪录
- 002概率论基本公式
热门文章
- WPS AI 详细体验教程!手把手教你体验AI
- Live555 直播性能优化
- 手游国际服吃鸡服务器维护,吃鸡手游国际服再次联动,这次有“大BOSS”!也许是新的PVE模式...
- PVN3D 配置过程(适用ubuntu20,适用30系显卡)
- 【Android】系统自带的主题与样式(theme and style)
- 算法 放置奇兵 如何配置使算力最大化 十星合成公式 十五星合成公式(升级、赋能)
- cocos creator小游戏案例之橡皮怪
- mysql 报wait millis 60000, active 0, maxActive 50, creating 0, createErrorCount 9913 错误 解决记录
- 人工智能,机器学习,深度学习,神经网络,四者的含义和关系
- 【调剂】北京交通大学在职专业学位中心2023年硕士研究生复试录取工作办法