.NET实体框架 - IEnumerable与IQueryable的区别

30
请看这行代码。这是调用存储过程,其返回值为ObjectResult<long?>。为了提取这些长整型数值,我添加了Select语句:
dbContext.FindCoursesWithKeywords(keywords).Select(l => l.Value);

根据智能感知,此Select返回 IEnumerable<long>

我不确定我是否在某个地方读过这个信息,或者可能只是习惯了这个假设 - 我一直认为,当EF API返回一个 IEnumerable(而不是 IQueryable)时,结果已经被材料化。这意味着它们已经从数据库中提取出来了。

今天我发现我错了(或者可能是个bug?)。我一直收到错误:

"New transaction is not allowed because there are other threads running in the session"

基本上,这个错误告诉你,在db reader仍在读取记录时,你正在尝试保存更改。

最终我通过(我认为是冒险行为),并添加了 ToArray() 调用来材料化 IEnumerable<long>...

所以 - 底线是 - 我应该期望从EF获得的 IEnumerable 结果包含尚未材料化的结果吗?如果是,那么有没有一种方法可以知道是否已经材料化了 IEnumerable

谢谢,如果这是一个“duhhh”问题,那我很抱歉... :)


我不确定是否有关于EF的具体文档,但对于Linq而言,一个IEnumerable<T>和一个IQueryable<T>一样都被普遍认为使用延迟执行,因此都没有被“实现”。 - Damien_The_Unbeliever
@Damien_The_Unbeliever:然而,我认为ObjectResult<T>将在调用函数时始终执行...(在这种情况下是FindCoursesWithKeywords) - Edwin de Koning
4个回答

33

IQueryable用于使用Linq-to-entities,即在应用程序中构建声明性的LINQ查询,该查询将被LINQ提供程序解释为SQL并在服务器上执行。一旦查询被执行(迭代),它将变成IEnumerable,对象将根据需要进行实例化,而不是立即实例化。

一旦调用存储过程,您就不再使用Linq-to-entities,因为在应用程序中没有构建声明性的查询。查询/ SQL已经存在于数据库服务器上,并且您只是在调用它。这将返回IEnumerable,但结果不会立即实例化。结果将作为迭代逐个实例化。这是数据库游标/或.NET数据读取器的原则,当您明确要求获取对象时。

所以,如果您像这样调用某些内容:

foreach (var keyword in dbContext.FindCoursesWithKeywords(keywords)
                                 .Select(l => l.Value))
{
    ...   
}

您正在逐个获取课程(顺便问一下,如果您只对关键字感兴趣,为什么要加载整个课程呢?)。在完成或中断循环之前,数据读取器一直处于打开状态以获取记录。

如果您改为调用以下内容:

foreach (var keyword in dbContext.FindCoursesWithKeywords(keywords)
                                 .ToList() // or ToArray 
                                 .Select(l => l.Value))
{
    ...
}
你将强制查询立即实现所有结果,循环将在内存中的集合上执行,而不是在打开的数据库读取器上执行。 IEnumerableIQueryable之间的区别不在于数据获取的方式,因为IQueryableIEnumerable。区别在于支持构造(必须有某些东西实现这些接口)。

嗨,感谢这个解释!因此,由于这是一个存储过程,它不会返回IQueryable,因为它不支持查询的所有功能,对吧?(顺便说一下,我正在获取课程而不是关键词) :) - justabuzz

12

对于一个 IEnumerable<T> 的操作将会在 C# 代码中进行,也就是说使用 linq-to-objects 进行操作。但这并不意味着查询已经执行。

一旦降级到 linq-to-objects,所有数据都需要从数据库中获取并发送到 .net。这可能会严重影响性能(例如,数据库索引不会被 linq-to-objects 使用),但另一方面,linq-to-objects 更加灵活,因为它可以执行任意的 C# 代码,而不受你的 linq 提供程序可以翻译成 SQL 的限制。

IEnumerable<T> 可以是一个延迟查询或已经实现的数据。标准的 linq 运算符通常是延迟的,而 ToArray()/ToList() 则总是实现的。


谢谢您的区分!那么,这是我应该注意的事情吗?有什么影响或需要特别注意的地方吗?干杯! - justabuzz
最明显的问题是,降级为 IEnumerable<T> 可以极大地降低性能。想象一下,如果你在查询的开头就进行了降级,那么很可能整个表格都需要被获取,而不是使用数据库上的索引进行过滤以加快速度。因此,在你降级为 IEnumerable<T> 的时候,希望剩余的项尽可能少。 - CodesInChaos
我不太确定这是否正确。我做了进一步的阅读,加上这里的另一个答案说了类似的事情 - IEnumerable<T> 也可能在迭代时才实现。如果我理解正确的话,这是因为当你执行存储过程时,你会得到一个完整的IQueryable,因为它不是一个完整的可查询对象。有道理吧? :) - justabuzz
正如我所说,IEnumerable<T> 的执行可能会被延迟,但不一定要被延迟。这取决于实现它的类。但是,如果您例如在 IEnumerable<T> 上执行 Where 子句,即使不满足条件的元素也需要逐个发送到 C# 并进行测试。但是我不知道存储过程返回什么,因为我从未使用过它们。 - CodesInChaos

0

IEnumerable在未被实例化前不会被填充。如果调用存储过程,我认为不需要进一步的筛选,因为您可以向存储过程发送参数以生成所需的数据子集。IEnumerable与存储过程绑定是可以的。但是,如果您正在获取整个表的内容,然后在应用程序中进行筛选,您应该有一个策略。例如,不要将表的IEnumerable转换为ToList(),这样会使所有行都被实例化。有时,这会导致内存溢出异常。此外,为什么没有必要的情况下消耗内存呢?使用IQueryable针对上下文,这样您就可以在数据源中过滤表,而不是在应用程序中过滤。回答问题,您必须将其实例化。IEnumerable是一个接口,只有将其实例化才能“初始化”其类型并在我的理解中产生结果。


-5

IEnumerable:LINQ to Object和LINQ to XML。

IQueryable:LINQ to SQL。


IQueryable是一种尚未执行的类型,基本上它就是“查询”。它与LINQ to SQL或Linq to Anything几乎没有任何关系。它是一个接口,用于提供对任何类型数据提供程序的访问。http://msdn.microsoft.com/en-us/library/system.linq.iqueryable(v=vs.100).ASPX - Tony

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接