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

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


当前回答

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

区别在于IQueryable<T>是允许LINQ-to- sql (LINQ. sql)的接口。对任何事情)去工作。因此,如果在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>可以避免从数据库返回太多行。另一个主要的例子是分页:如果你在IQueryable上使用Take和Skip,你将只得到请求的行数;在IEnumerable< t>上这样做会导致你所有的行都被载入内存。

其他回答

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

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

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

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

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

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

选择什么?

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

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

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

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

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

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

我想澄清一些事情,因为似乎相互矛盾的反应(主要围绕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中包含相关实体。

有一篇博客文章有简短的源代码示例,介绍了滥用IEnumerable<T>如何极大地影响LINQ查询性能:实体框架: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 source中指定的查询提供程序创建查询。

上面的答案很好,但它没有提到解释两个接口“如何”不同的表达式树。基本上,有两组相同的LINQ扩展。Where(), Sum(), Count(), FirstOrDefault()等都有两个版本:一个接受函数,一个接受表达式。

IEnumerable版本签名为:Where(Func<Customer, bool> predicate) IQueryable版本签名是:Where(表达式<Func<Customer, bool>>谓词)

你可能在没有意识到的情况下使用了这两个函数,因为它们使用相同的语法调用:

例如,Where(x => x.City == "<City>")对IEnumerable和IQueryable都有效

当在IEnumerable集合上使用Where()时,编译器将一个编译后的函数传递给Where() 当在IQueryable集合上使用Where()时,编译器将表达式树传递给Where()。表达式树类似于反射系统,但用于代码。编译器将代码转换为数据结构,该结构以易于理解的格式描述代码的功能。

为什么要用这个表达式树呢?我只想让Where()过滤我的数据。 主要原因是EF和Linq2SQL orm都可以将表达式树直接转换为SQL,这样您的代码将执行得更快。

哦,这听起来像一个免费的性能提升,我应该在这种情况下使用AsQueryable()吗? 不,IQueryable只有在底层数据提供程序可以使用它时才有用。将常规列表转换为IQueryable不会给你带来任何好处。