返回IEnumerable <t>与IQueryable <t> </t> </t>

时间:2010-05-20 18:14:00

标签: c# linq linq-to-sql ienumerable iqueryable

返回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;

两者都会延迟执行,何时应优先于另一方?

16 个答案:

答案 0 :(得分:1662)

是的,两者都会给你deferred execution

不同之处在于IQueryable<T>是允许LINQ-to-SQL(LINQ.-to-anything真正)工作的接口。因此,如果您在IQueryable<T>上进一步优化查询,则该查询将在数据库中执行(如果可能)。

对于IEnumerable<T>情况,它将是LINQ-to-object,这意味着必须将与原始查询匹配的所有对象从数据库加载到内存中。

在代码中:

IQueryable<Customer> custs = ...;
// Later on...
var goldCustomers = custs.Where(c => c.IsGold);

该代码将执行SQL以仅选择黄金客户。另一方面,以下代码将在数据库中执行原始查询,然后过滤掉内存中的非黄金客户:

IEnumerable<Customer> custs = ...;
// Later on...
var goldCustomers = custs.Where(c => c.IsGold);

这是一个非常重要的区别,在IQueryable<T>上工作可以在很多情况下避免从数据库中返回太多行。另一个主要的例子是进行分页:如果您在Take上使用SkipIQueryable,则只会获得请求的行数;在IEnumerable<T>上执行此操作将导致所有行都加载到内存中。

答案 1 :(得分:266)

最好的答案是好的,但它没有提到表达树,它解释了&#34;如何&#34;这两个接口不同。基本上,有两组相同的LINQ扩展。 Where()Sum()Count()FirstOrDefault()等都有两个版本:一个接受函数,另一个接受表达式。

  • IEnumerable版本签名为:Where(Func<Customer, bool> predicate)

  • IQueryable版本签名为:Where(Expression<Func<Customer, bool>> predicate)

您可能一直在使用这两者而没有意识到它,因为两者都使用相同的语法调用:

e.g。 Where(x => x.City == "<City>")适用于IEnumerableIQueryable

  • Where()集合上使用IEnumerable时,编译器会将已编译的函数传递给Where()

  • Where()集合上使用IQueryable时,编译器会将表达式树传递给Where()。表达式树就像反射系统,但代码。编译器将您的代码转换为数据结构,以便以易于理解的格式描述您的代码所执行的操作。

为什么要打扰这个表达式树的东西?我只想Where()来过滤我的数据。 主要原因是EF和Linq2SQL ORM都可以将表达式树直接转换为SQL,您的代码执行速度会快得多。

哦,这听起来像一个免费的性能提升,我应该在这种情况下使用AsQueryable()吗? 不,IQueryable仅在基础数据提供者可以对其执行某些操作时才有用。将普通List之类的内容转换为IQueryable不会给您带来任何好处。

答案 2 :(得分:72)

是的,两者都使用延迟执行。让我们用SQL Server探查器来说明差异......

当我们运行以下代码时:

MarketDevEntities db = new MarketDevEntities();

IEnumerable<WebLog> first = db.WebLogs;
var second = first.Where(c => c.DurationSeconds > 10);
var third = second.Where(c => c.WebLogID > 100);
var result = third.Where(c => c.EmailAddress.Length > 11);

Console.Write(result.First().UserName);

在SQL Server探查器中,我们找到一个等于:

的命令
"SELECT * FROM [dbo].[WebLog]"

对于具有100万条记录的WebLog表,运行该代码块大约需要90秒。

因此,所有表记录都作为对象加载到内存中,然后每个.Where()将是内存中针对这些对象的另一个过滤器。

在上面的示例(第二行)中使用IQueryable代替IEnumerable时:

在SQL Server探查器中,我们找到一个等于:

的命令
"SELECT TOP 1 * FROM [dbo].[WebLog] WHERE [DurationSeconds] > 10 AND [WebLogID] > 100 AND LEN([EmailAddress]) > 11"

使用IQueryable运行此代码块大约需要4秒钟。

IQueryable有一个名为Expression的属性,它存储一个树表达式,当我们在我们的示例中使用result(称为延迟执行)时,它开始被创建,最后这个表达式将是转换为SQL查询以在数据库引擎上运行。

答案 3 :(得分:55)

两者都会给你延期执行,是的。

至于哪一个优先于另一个,它取决于您的基础数据源是什么。

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

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

答案 4 :(得分:27)

一般来说,我会推荐以下内容:

  • 如果您想使用您的方法启用开发人员来优化您在执行之前返回的查询,请返回IQueryable<T>

  • 如果要传输一组对象进行枚举,请返回IEnumerable

想象一下IQueryable就像它是什么 - 数据的“查询”(如果你愿意,你可以改进)。 IEnumerable是一组可以枚举的对象(已经收到或已创建)。

答案 5 :(得分:25)

之前已经说了很多,但是以更技术性的方式回到了根源:

  1. IEnumerable 是内存中可以枚举的对象的集合 - 内存中的序列,可以迭代(在{{1}内轻松实现) }循环,虽然你只能使用foreach。它们按原样驻留在内存中。
  2. IEnumerator 是一个表达式树,它会在某些点上被翻译成其他内容,能够枚举最终结果。我想这就是让大多数人感到困惑的原因。
  3. 他们显然有不同的内涵。

    IQueryable表示一个表达式树(简单地称为查询),一旦调用发布API,它将被底层查询提供程序转换为其他内容树,如LINQ聚合函数(Sum,Count等)或ToList [Array,Dictionary,...]。 IQueryable对象也会实现IQueryableIEnumerable,以便 如果它们代表查询 ,则可以迭代该查询的结果。这意味着IQueryable不仅仅是查询。正确的术语是表达树

    现在,如何执行这些表达式以及它们转向的是所谓的查询提供程序(我们可以认为是表达式执行程序)。

    Entity Framework世界(即神秘的基础数据源提供程序或查询提供程序)IEnumerable<T>表达式被转换为本机T-SQL查询。 IQueryable与他们做同样的事情。例如,您可以按照LINQ: Building an IQueryable Provider链接中详细描述的概念编写自己的概念,并且您可能希望为产品商店提供商服务提供自定义查询API。

    基本上,Nhibernate对象一直在构建,直到我们显式释放它们并告诉系统将它们重写为SQL或其他任何东西并向下发送执行链以进行后续处理。

    好像要 延迟 执行它的sa IQueryable功能,以便在内存中保留表达式树方案并将其发送到执行中按需调用某些API(相同的Count,ToList等)。

    两者的正确使用在很大程度上取决于您针对特定案例所面临的任务。对于众所周知的存储库模式,我个人选择返回LINQ,即IList优先于列表(索引器等)。因此,我的建议是仅在存储库中使用IEnumerable,在代码中的任何其他位置使用IEnumerable。没有说IQueryable分解并破坏separation of concerns原则的可测性问题。如果从存储库中返回表达式,则消费者可以按照自己的意愿使用持久层。

    对乱七八糟的补充:)(来自评论中的讨论)) 它们都不是记忆中的对象,因为它们本身并不是真正的类型,它们是一种类型的标记 - 如果你想要那么深。但它有意义(甚至是MSDN这样做的原因)将IEnumerables视为内存中的集合,而将IQueryables视为表达式树。关键是IQueryable接口继承IEnumerable接口,因此如果它表示查询,则可以枚举该查询的结果。枚举导致与IQueryable对象关联的表达式树被执行。 所以,事实上,你不能在没有内存中的对象的情况下调用任何IEnumerable成员。如果你这样做,它会进入那里,无论如何,如果它不是空的。 IQueryables只是查询,而不是数据。

答案 6 :(得分:24)

通常,您希望保留查询的原始静态类型,直到重要为止。

出于这个原因,您可以将变量定义为“var”而不是IQueryable<>IEnumerable<>,并且您将知道您没有更改类型。

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

将类型从IQueryable<>更改为IEnumerable<>的一个令人信服的理由可能是您正在调用某个扩展函数,即您的特定对象中IQueryable<>的实现无法处理或处理效率低下。在这种情况下,您可能希望将类型转换为IEnumerable<>(通过分配类型IEnumerable<>的变量或使用AsEnumerable扩展方法),以便扩展功能为您调用最终成为Enumerable类而不是Queryable类。

答案 7 :(得分:18)

有一篇博客文章,其中简要介绍了IEnumerable<T>的滥用如何极大地影响LINQ查询效果:Entity Framework: IQueryable vs. IEnumerable

如果我们深入挖掘并深入研究来源,我们可以看到IEnumerable<T>有明显不同的扩展方法:

// Type: System.Linq.Enumerable
// Assembly: System.Core, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
// Assembly location: C:\Windows\Microsoft.NET\Framework\v4.0.30319\System.Core.dll
public static class Enumerable
{
    public static IEnumerable<TSource> Where<TSource>(
        this IEnumerable<TSource> source, 
        Func<TSource, bool> predicate)
    {
        return (IEnumerable<TSource>) 
            new Enumerable.WhereEnumerableIterator<TSource>(source, predicate);
    }
}

IQueryable<T>

// Type: System.Linq.Queryable
// Assembly: System.Core, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
// Assembly location: C:\Windows\Microsoft.NET\Framework\v4.0.30319\System.Core.dll
public static class Queryable
{
    public static IQueryable<TSource> Where<TSource>(
        this IQueryable<TSource> source, 
        Expression<Func<TSource, bool>> predicate)
    {
        return source.Provider.CreateQuery<TSource>(
            Expression.Call(
                null, 
                ((MethodInfo) MethodBase.GetCurrentMethod()).MakeGenericMethod(
                    new Type[] { typeof(TSource) }), 
                    new Expression[] 
                        { source.Expression, Expression.Quote(predicate) }));
    }
}

第一个返回可枚举迭代器,第二个通过查询提供程序创建查询,在IQueryable源中指定。

答案 8 :(得分:11)

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

我是EF和Linq for Entities的新手,因此需要一段时间来弄清楚瓶颈是什么。使用MiniProfiling,我找到了查询,然后将所有单个操作转换为单个IQueryable Linq for Entities查询。 IEnumerable耗时15秒,IQueryable耗时0.5秒。涉及到三个表,在阅读本文之后,我认为IEnumerable查询实际上形成了一个三表交叉产品并过滤了结果。

尝试使用IQueryables作为经验法则并对您的工作进行分析,以使您的更改可衡量。

答案 9 :(得分:11)

这些是IQueryable<T>IEnumerable<T>

之间的一些差异

difference between returning IQueryable<T> vs. IEnumerable<T>

答案 10 :(得分:10)

我想澄清一些看似相互矛盾的反应(主要是围绕IEnumerable)。

(1)IQueryable扩展了IEnumerable接口。 (您可以将IQueryable发送到期望IEnumerable没有错误的内容。)

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

换句话说,IEnumerables不仅仅是&#34;在记忆中&#34;。 IQueryables并不总是在数据库上执行。 IEnumerable必须将内容加载到内存中(一旦检索,可能会延迟),因为它没有抽象数据提供程序。 IQueryables依赖于抽象提供程序(如LINQ-to-SQL),尽管这也可能是.NET内存提供程序。

示例用例

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

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

(c)从视图中迭代并访问数据集的记录,子实体和属性。 (可能会导致异常!)

可能出现的问题

(1)IEnumerable尝试延迟加载,您的数据上下文已过期。抛出异常,因为提供程序不再可用。

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

(3)多个活动结果集(MARS)。如果您在IEnumerable块中迭代foreach( var record in resultSet )并同时尝试访问record.childEntity.childProperty,则由于数据集和关系实体的延迟加载,您可能最终得到MARS。如果未在连接字符串中启用,则会导致异常。

<强>解决方案

  • 我发现在连接字符串中启用MARS的工作不可靠。我建议你避免使用MARS,除非它被充分理解并明确要求。

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

如果您正在访问相关实体,您可能仍需要数据上下文。或者,您可以从Include禁用实体代理和明确DbSet相关实体。

答案 11 :(得分:9)

“IEnumerable”和“IQueryable”之间的主要区别在于执行过滤器逻辑的位置。一个在客户端执行(在内存中),另一个在数据库上执行。

例如,我们可以考虑一个示例,其中我们的数据库中有一个用户的10,000条记录,让我们说只有900条是活跃用户,所以在这种情况下,如果我们使用“IEnumerable”,那么首先加载所有10,000条记录在内存中然后对其应用IsActive过滤器,最终返回900个活动用户。

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

参考Link

答案 12 :(得分:5)

我们可以以相同的方式使用它们,它们只是性能不同。

IQueryable只能以有效的方式对数据库执行。这意味着它创建了一个完整的选择查询,只获取相关记录。

例如,我们希望采用名称以“Nimal”开头的前10名客户。在这种情况下,选择查询将生成为select top 10 * from Customer where name like ‘Nimal%’

但是如果我们使用IEnumerable,查询将类似于select * from Customer where name like ‘Nimal%’,前十名将在C#编码级别进行过滤(它从数据库获取所有客户记录并将其传递给C#)。

答案 13 :(得分:5)

除了前2个非常好的答案(通过driis&amp; by Jacob):

  

的IEnumerable   interface位于System.Collections命名空间中。

IEnumerable对象表示内存中的一组数据,只能向前移动此数据。 IEnumerable对象表示的查询是立即完全执行的,因此应用程序可以快速接收数据。

执行查询时,IEnumerable加载所有数据,如果需要对其进行过滤,则过滤本身在客户端完成。

  

IQueryable接口位于System.Linq命名空间中。

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

选择什么?

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

如果您不需要整套返回数据,只需要一些过滤后的数据,那么最好使用IQueryable。

答案 14 :(得分:0)

除上述内容外,有趣的是,如果您使用IQueryable而不是IEnumerable,则会获得例外:

如果productsIEnumerable,则以下命令可以正常工作:

products.Skip(-4);

但是,如果productsIQueryable,并且它正尝试从数据库表访问记录,则会出现此错误:

  

在OFFSET子句中指定的偏移量不能为负。

这是因为构造了以下查询:

SELECT [p].[ProductId]
FROM [Products] AS [p]
ORDER BY (SELECT 1)
OFFSET @__p_0 ROWS

和OFFSET不能为负值。

答案 15 :(得分:-1)

IEnumrable会将数据存储到内存中

但是如果是不可数的,它不会存储到内存中

有关更多详细信息,请使用sql profiler检查

第一时间打到你  用IQueryable查询  并查看执行什么查询

然后尝试从IEnumrable