在C#中使用 Linq to sql 时,经常搞混 IQueryableIEnumerable 这两种类型,本文简单分析下它们之间的区别和使用场景。

前言

不管是Linq to object,还是Linq to sqlLinq to EntityIQueryableIEnumerable都是延迟执行的,它们之间的区别仅仅在于扩展方法的参数类型不同。(迭代/枚举方式不同?作用对象不同?)

IQueryableIEnumerable 的区别

  • IQueryable:扩展方法接受的是Expression

    对于Linq to sqlLinq to EntityExpression必须要能转成sql,否则会报错。

  • IEnumerable:扩展方法接受的是Func(Func是C#语法)。

    IEnumerable跑的是Linq to Object,会强制从数据库中读取所有数据到内存里,所以可以使用C#语法。

    对于Linq to sqlLinq to EntityFunc是无限制的,因为它是使用C#语法操作数据的。这也从侧面说明:数据已经读取到内存中了。

    IEnumerable的扩展方法是Func,不会转换为sql。转换为sql的是内部的IQueryable。所以要注意条件最好限制在IQueryable里,否则IQueryable可能会读取大量数据,增加耗时和内存。

AsEnumerable()ToList() 的区别

  • ToList()立即执行。会立即执行sql,取出数据到内存中。

  • AsEnumerable()延迟执行,真正使用时才执行sql读取数据。此处有坑,一定要往下看

IQueryable对象使用AsEnumerable()后,仍然是延迟执行,不过此时对象本质已经变了

前面已经说了 IEnumerable的扩展方法接受的是Func(C#语法),当ie对象(iq转变) 真正使用时,会有2个步骤:

  1. 它会把iq对象(转变之前的) 的扩展方法翻译成sql语句,查询出数据加载到内存中,变为ie对象;
  2. 此时再把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 sqlEF(非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条件?不行,这么做太傻逼,网络数据传输增加;查询效率也低下;占用数据库连接池资源。种种缺点,简单问题复杂化。

由上面的反例可以看出,一条一条查数据可以实现,但是太二逼。完全不如一次性全部读取数据的好。

其他

IQueryableIEnumerable生成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 的区别相关推荐

  1. C# IQueryable 和 IEnumerable 的区别

    这是 EF Core 系列的最后一篇文章,按照上一篇的计划,我们最后就讲一讲 IQueryable 和 IEnumerable 的区别. 点击上方或后方蓝字,阅读 EF Core 系列合集. 在前面的 ...

  2. 【C#】IQueryable和IEnumerable的区别

    IEnumerable接口 公开枚举器,该枚举器支持在指定类型的集合上进行简单迭代.也就是说:实现了此接口的object,就可以直接使用foreach遍历此object: IQueryable 接口 ...

  3. EFCore——IQueryable与IEnumerable的区别(13)

    IQueryable与IEnumerable的区别 一.IQueryable与IEnumerable的简单实例 二.IQueryable与IEnumerable的区别 一.IQueryable与IEn ...

  4. C#中IQueryable和IEnumerable的区别

    IQueryable接口是继承自IEnumerable的接口 IQueryable中有表达式树, 这可以看作是它的一个优势.所以,使用IQueryable操作时,比如对数据的过滤,排序等操作, 这些都 ...

  5. IQueryable和IEnumerable的区别

    转载于:https://www.cnblogs.com/myyBlog/p/8961062.html

  6. IQueryable和IEnumerable,IList的区别

    IQueryable和IEnumerable都是延时执行(Deferred Execution)的,而IList是即时执行(Eager Execution) IQueryable和IEnumerabl ...

  7. EntityFramework中IEnumerable和IQueryable的含义和区别

    先说下IList,IList对SQL语句是即时执行的,IEnumerable和IQueryable是延时执行的,用到才执行. IQueryable和IEnumerable在每次执行时都必须连接数据库读 ...

  8. 对IQueryable和IEnumerable的认识

    ** IQueryable和IEnumerable的区别 public static IQueryable<TSource> Where<TSource>(this IQuer ...

  9. IQueryable和IEnumerable区别

    IQueryable 和IEnumerable总结 1,IEnumerable<T> result = (from t in context.Table  order by t.Id se ...

最新文章

  1. matlab 自动控制仿真,Matlab在自动控制系统建模与仿真中的应用
  2. Kinect SDK V1.7 开发工具包概览
  3. c6011取消对null指针的引用_C++中的野指针及其规避方法
  4. 金蝶国际公布2020年全年业绩,云业务收入增长45.6%
  5. linux mysql 5.6.23_mysql 5.6.23 的安装
  6. 转 mysql处理高并发,防止库存超卖
  7. java 指针 引用_java中的引用与c中的指针
  8. C++中的struct与class继承方式
  9. IT人看《国富论》系列:第一篇之第二章:论分工的原由。分工其实是人类利己倾向的结果...
  10. gwas snp 和_Science | 群体研究新思路:De novo + GWAS
  11. Lwip协议详解(基于Lwip 2.1.0)TCP协议 (未完待续)
  12. 【STM32H7的DSP教程】第13章 DSP快速计算函数-三角函数和平方根
  13. 【Unity】Unity Shader学习笔记(一)Unity Shader基础
  14. QQ等App每天自启百次;​李国庆直播拍卖午餐时间1小时;苹果提交认证9款新手机 | EA周报...
  15. 电子表格软件2013 免费版
  16. python程序员面试算法宝典pdf-Python程序员面试笔试宝典
  17. html中常用判断和工具(二)
  18. IOS中__bridge,__bridge_retained和__bridge_transfer理解
  19. 蒙特卡洛随机模拟的MATLAB实例解析纪录
  20. 002概率论基本公式

热门文章

  1. WPS AI 详细体验教程!手把手教你体验AI
  2. Live555 直播性能优化
  3. 手游国际服吃鸡服务器维护,吃鸡手游国际服再次联动,这次有“大BOSS”!也许是新的PVE模式...
  4. PVN3D 配置过程(适用ubuntu20,适用30系显卡)
  5. 【Android】系统自带的主题与样式(theme and style)
  6. 算法 放置奇兵 如何配置使算力最大化 十星合成公式 十五星合成公式(升级、赋能)
  7. cocos creator小游戏案例之橡皮怪
  8. mysql 报wait millis 60000, active 0, maxActive 50, creating 0, createErrorCount 9913 错误 解决记录
  9. 人工智能,机器学习,深度学习,神经网络,四者的含义和关系
  10. 【调剂】北京交通大学在职专业学位中心2023年硕士研究生复试录取工作办法