2011-09-30 137 views
23

请看这行代码。这是一个存储过程的调用,它返回ObjectResult<long?>。为了提取长值,我增加了选择:.NET实体框架 - IEnumerable VS. IQueryable

dbContext.FindCoursesWithKeywords(keywords).Select(l => l.Value); 

基于智能感知这个选择返回IEnumerable<long>

我不知道我是在某处阅读,还是习惯了这个假设 - 我一直认为当EF API返回IEnumerable(而不是)时,这意味着结果已经实现。意思是他们已经从数据库中提取出来了。

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

“新的事物是不允许的,因为有其他线程 在会话中运行”

基本上,这个错误告诉你,你想救而变化数据库读取器仍在读取记录。

最后我解决它的(我认为是一个长镜头),并加入ToArray()呼叫物化为IEnumerable<long> ...

所以 - 底线 - 我应该期待EF IEnumerable结果包含结果避风港物化了吗?如果是,那么有没有办法知道IEnumerable是否已经实现?

感谢,如果是这样的那些“duhhh”的问题之一道歉... :)

+0

我不确定是否有任何特别记录的EF,但是对于Linq来说,'IEnumerable '不比'IQueryable '更具体化 - 两者通常都假定使用延迟执行。 –

+0

@Damien_The_Unbeliever:但是,我认为ObjectResult 将始终执行时调用该函数...(在这种情况下FindCoursesWithKeywords) –

回答

27

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。区别在于支持结构(必须实现这些接口)。

+0

嗨,感谢您的解释!因此,由于这是一个存储过程,它不会返回IQueryable,因为它不支持查询所做的所有功能,对吧? (顺便说一句,我提取的课程不是关键字):) – justabuzz

10

IEnumerable<T>工作意味着所有进一步的操作将在C#代码,即LINQ到对象发生。这并不意味着查询已经执行。

一旦你降级到linq-to-objects,此时剩下的所有数据都需要从数据库中提取并发送到.net。这会严重降低性能(例如,数据库索引不会被linq-to-objects使用),但另一方面,linq-to-objects更灵活,因为它可以执行任意C#代码,而不受限于什么您的linq提供程序可以转换为SQL。

A IEnumerable<T>可以是延迟查询或已经物化的数据。标准的linq运营商通常会被推迟,并且总是会实现ToArray()/ToList()

+0

感谢您的区别!那么,我应该注意的是什么?是否有任何影响或陷阱?干杯! – justabuzz

+0

最明显的问题是退化为'IEnumerable '会大幅降低性能。想象一下,在查询开始的时候你会降级,那么很可能需要提取整个表,而不是使用服务器上的SQL使用数据库上的索引进行过滤以加速。所以在你降级到'IEnumerable '的时候,你希望尽可能少的东西剩下。 – CodesInChaos

+0

不太确定这是否正确。进一步阅读我做了+在这里的其他答案说一个类似的东西 - IEnumerable 也可能不会实现,直到后来当你迭代它。如果我明白这是正确的,这是因为当你执行一个存储过程时,你会得到一个完整的IQueryable,因为这不是一个完整的可查询对象。说得通? :) – justabuzz

-5

IEnumerable:LINQ to Object和LINQ to XML。

IQueryable:LINQ to SQL

+0

IQueryable是一种尚未执行的类型。基本上它是“查询”。 http://msdn.microsoft.com/en-us/library/system.linq.iqueryable(v=vs.100).ASPX 病毒与LINQ to SQL或Linq to Anything无关。它是提供对任何类型的数据提供者访问的接口。 – Tony

0

IEnumerable将不会水合,直到物化。如果调用一个存储过程,我会认为没有其他过滤器需要,我的意思是你发送参数到一个存储过程来产生返回的所需数据子集。与存储过程绑定的IEnumerable没问题。但是,如果您要获取表格的全部内容,那么在应用程序中筛选应该有一个策略。就像,不要ToList()表的IEnumerable,你将实现所有行。有时候这会导致内存不足异常。此外为什么消耗记忆没有理由。对上下文使用IQueryable,这样你可以在数据源过滤表,而不是在应用程序中。作为回答,你必须实现它。 IEnumerable是一个接口,只有实现它才能“初始化”它的类型并产生我理解的东西。