返回IQueryable<T>和IEnumerable<T>之间的区别是什么,什么时候应该优先于另一个?

IQueryable<Customer> custs = from c in db.Customers
where c.City == "<City>"
select c;

IEnumerable<Customer> custs = from c in db.Customers
where c.City == "<City>"
select c;

两者都将被延迟执行,何时应该优先于其中一个?


当前回答

我想澄清一些事情,因为似乎相互矛盾的反应(主要围绕IEnumerable)。

IQueryable扩展了IEnumerable接口。(你可以发送一个IQueryable给一个期望IEnumerable而没有错误的对象。)

(2) IQueryable和IEnumerable LINQ在迭代结果集时都尝试延迟加载。(注意,可以在每种类型的接口扩展方法中看到实现。)

换句话说,IEnumerables不是独占的“内存中”。IQueryables并不总是在数据库上执行。IEnumerable必须将数据加载到内存中(一旦检索到,可能是惰性的),因为它没有抽象数据提供程序。IQueryables依赖于抽象提供者(如LINQ-to-SQL),尽管这也可以是. net内存中的提供者。

样例用例

(a)从EF上下文检索IQueryable记录列表。(内存中没有记录。)

(b)将IQueryable传递给模型为IEnumerable的视图。(有效。IQueryable扩展IEnumerable。)

(c)在视图中遍历并访问数据集的记录、子实体和属性。(可能会导致异常!)

可能的问题

IEnumerable尝试延迟加载,你的数据上下文过期了。由于提供程序不再可用而引发异常。

(2)实体框架实体代理已启用(默认),并且您尝试访问具有过期数据上下文的相关(虚拟)对象。同(1)。

(3)多活动结果集(MARS)。如果你在foreach(var record in resultSet)块中遍历IEnumerable,同时尝试访问record. childentity。childProperty,由于数据集和关系实体的惰性加载,您可能最终会得到MARS。如果在连接字符串中未启用该选项,则会导致异常。

解决方案

我发现在连接字符串中启用MARS工作不可靠。我建议你避免MARS,除非它被很好地理解和明确地渴望。

执行查询并通过调用resultList = resultSet.ToList()来存储结果,这似乎是确保实体位于内存中的最直接的方法。

在访问相关实体的情况下,可能仍然需要数据上下文。或者,您可以禁用实体代理,并显式地从DbSet中包含相关实体。

其他回答

一般来说,您希望保留查询的原始静态类型,直到有必要。

出于这个原因,你可以将你的变量定义为'var',而不是IQueryable<>或IEnumerable<>,你会知道你没有改变类型。

如果您开始使用IQueryable<>,通常希望将其保留为IQueryable<>,直到有一些令人信服的理由更改它。这样做的原因是您希望为查询处理器提供尽可能多的信息。例如,如果你只打算使用10个结果(你已经调用Take(10)),那么你想让SQL Server知道这一点,这样它就可以优化它的查询计划,只向你发送你将使用的数据。

A compelling reason to change the type from IQueryable<> to IEnumerable<> might be that you are calling some extension function that the implementation of IQueryable<> in your particular object either cannot handle or handles inefficiently. In that case, you might wish to convert the type to IEnumerable<> (by assigning to a variable of type IEnumerable<> or by using the AsEnumerable extension method for example) so that the extension functions you call end up being the ones in the Enumerable class instead of the Queryable class.

都能让你延期执行,没错。

至于哪个是首选的,这取决于您的底层数据源是什么。

返回一个IEnumerable将自动强制运行时使用LINQ to Objects来查询您的集合。

返回一个IQueryable(顺便说一下,它实现了IEnumerable)提供了额外的功能,可以将您的查询转换为可能在底层源上执行得更好的查询(LINQ to SQL, LINQ to XML,等等)。

“IEnumerable”和“IQueryable”之间的主要区别在于在哪里执行筛选逻辑。一个在客户端(内存中)执行,另一个在数据库中执行。

例如,我们可以考虑一个例子,我们的数据库中有一个用户的10,000条记录,假设只有900个是活动用户,所以在这种情况下,如果我们使用“IEnumerable”,那么首先它将所有10,000条记录加载到内存中,然后应用IsActive筛选器,最终返回900个活动用户。

而另一方面,在同样的情况下,如果我们使用“IQueryable”,它将直接应用数据库上的IsActive过滤器,直接从那里将返回900个活跃用户。

我最近遇到了IEnumerable v. IQueryable问题。所使用的算法首先执行IQueryable查询以获得一组结果。然后将这些元素传递给foreach循环,并将这些元素实例化为实体框架(Entity Framework, EF)类。然后在Linq to Entity查询的from子句中使用这个EF类,导致结果为IEnumerable。

我对EF和实体的Linq相当陌生,所以花了一段时间才弄清楚瓶颈是什么。使用MiniProfiling,我找到了查询,然后将所有单独的操作转换为单个IQueryable Linq for Entities查询。IEnumerable执行了15秒,IQueryable执行了0.5秒。其中涉及三个表,在阅读本文后,我相信IEnumerable查询实际上形成了三个表的交叉积并过滤了结果。

尝试使用IQueryables作为经验法则,分析您的工作,使您的更改可测量。

除了前两个非常好的答案(由driis和Jacob):

IEnumerable 接口在系统中。集合名称空间。

IEnumerable对象表示内存中的一组数据,并且只能根据这些数据向前移动。由IEnumerable对象表示的查询立即完整地执行,因此应用程序可以快速地接收到数据。

当执行查询时,IEnumerable加载所有数据,如果我们需要对其进行筛选,筛选本身是在客户端完成的。

可查询接口位于系统中。Linq命名空间。

IQueryable对象提供了对数据库的远程访问,并允许您以从头到尾的直接顺序或反向顺序在数据中导航。在创建查询的过程中,返回的对象是IQueryable,查询是优化的。因此,在执行过程中消耗的内存更少,网络带宽更少,但同时它的处理速度比返回IEnumerable对象的查询稍微慢一些。

选择什么?

如果您需要返回的整个数据集,那么最好使用IEnumerable,它提供了最大的速度。

如果你不需要整个返回的数据集,而只是一些过滤后的数据,那么最好使用IQueryable。