.ToList(),.AsEnumerable(),AsQueryable()之间有什么区别?

我知道一些LINQ to Entities和LINQ to Objects的区别,第一个实现了IQueryable ,第二个实现了IEnumerable ,我的问题范围在EF 5之内。

我的问题是这三种方法的技术差异是什么? 我看到,在许多情况下,所有这些工作。 我也看到使用它们的组合.ToList().AsQueryable()

  1. 这些方法究竟意味着什么?

  2. 是否有任何性能问题或会导致相互使用的问题?

  3. 为什么会使用.ToList().AsQueryable()而不是.AsQueryable()

对此有很多话要说。 让我关注一下AsEnumerableAsQueryable并提到ToList()

这些方法是做什么的?

AsEnumerableAsQueryable分别转换或转换为IEnumerableIQueryable 。 我说转换或转换的理由是:

  • 当源对象已经实现了目标接口时,源对象本身被返回,但转换到目标接口。 换句话说:types没有改变,但是编译时间types是。

  • 当源对象没有实现目标接口时,源对象被转换为实现目标接口的对象。 所以types和编译时间types都被改变了。

让我用一些例子来展示这一点。 我有一个小小的方法来报告编译时types和对象的实际types( Jon Skeet提供 ):

 void ReportTypeProperties<T>(T obj) { Console.WriteLine("Compile-time type: {0}", typeof(T).Name); Console.WriteLine("Actual type: {0}", obj.GetType().Name); } 

让我们尝试一个任意的linq-to-sql Table<T> ,它实现了IQueryable

 ReportTypeProperties(context.Observations); ReportTypeProperties(context.Observations.AsEnumerable()); ReportTypeProperties(context.Observations.AsQueryable()); 

结果:

 Compile-time type: Table`1 Actual type: Table`1 Compile-time type: IEnumerable`1 Actual type: Table`1 Compile-time type: IQueryable`1 Actual type: Table`1 

您会发现表类本身总是返回,但其表示方式发生了变化。

现在是一个实现IEnumerable而不是IQueryable的对象:

 var ints = new[] { 1, 2 }; ReportTypeProperties(ints); ReportTypeProperties(ints.AsEnumerable()); ReportTypeProperties(ints.AsQueryable()); 

结果:

 Compile-time type: Int32[] Actual type: Int32[] Compile-time type: IEnumerable`1 Actual type: Int32[] Compile-time type: IQueryable`1 Actual type: EnumerableQuery`1 

在那里。 AsQueryable()已将数组转换为EnumerableQuery ,“ EnumerableQuery ”将IEnumerable<T>集合表示为IQueryable<T>数据源。 (MSDN)。

有什么用?

AsEnumerable通常用于从任何IQueryable实现切换到LINQ到对象(L2O),主要是因为前者不支持L2O所具有的函数。 有关更多详细信息,请参阅LINQ实体上AsEnumerable()的作用是什么? 。

例如,在entity framework查询中,我们只能使用有限数量的方法。 因此,例如,如果我们需要在查询中使用我们自己的方法之一,我们通常会写类似的东西

 var query = context.Observations.Select(o => o.Id) .AsEnumerable().Select(x => MySuperSmartMethod(x)) 

IEnumerable<T>转换为List<T> ToList也经常用于此目的。 使用AsEnumerableToList的优点是AsEnumerable不执行查询。 AsEnumerable保留延迟执行,并不会构build一个通常无用的中间列表。

另一方面,当强制执行LINQ查询时, ToList可以成为一种方法。

AsQueryable可用于在LINQ语句中使可枚举集合接受expression式。 在这里看到更多的细节: 我真的需要在集合上使用AsQueryable()吗? 。

注意物质滥用!

AsEnumerable就像一种药物。 这是一个快速的解决scheme,但代价并不能解决潜在的问题。

在许多堆栈溢出的答案中,我看到应用AsEnumerable人解决了LINQexpression式中不支持的方法的任何问题。 但价格并不总是清楚。 例如,如果你这样做:

 context.MyLongWideTable // A table with many records and columns .Where(x => x.Type == "type") .Select(x => new { x.Name, x.CreateDate }) 

…一切都整齐地翻译成一个SQL语句, 筛选Where )和项目Select )。 也就是说,SQL结果集的长度和宽度都分别减less了。

现在假设用户只想查看CreateDate的date部分。 在entity framework,你会很快发现…

 .Select(x => new { x.Name, x.CreateDate.Date }) 

…不被支持(在撰写本文时)。 啊,幸运的是有一个AsEnumerable修复:

 context.MyLongWideTable.AsEnumerable() .Where(x => x.Type == "type") .Select(x => new { x.Name, x.CreateDate.Date }) 

当然,它可能运行。 但它将整个表格拉到内存中,然后应用filter和投影。 那么,大多数人都足够聪明,做的Where第一:

 context.MyLongWideTable .Where(x => x.Type == "type").AsEnumerable() .Select(x => new { x.Name, x.CreateDate.Date }) 

但是仍然所有的列首先被取出,并且投影在内存中完成。

真正的解决办法是:

 context.MyLongWideTable .Where(x => x.Type == "type") .Select(x => new { x.Name, DbFunctions.TruncateTime(x.CreateDate) }) 

(但是,这只需要一点点的知识…)

这些方法不做什么?

现在是一个重要的警告。 当你这样做

 context.Observations.AsEnumerable() .AsQueryable() 

您将以IQueryable表示的源对象结束。 (因为这两种方法只能投,不能转换)。

但是,当你这样做

 context.Observations.AsEnumerable().Select(x => x) .AsQueryable() 

结果会是什么?

Select产生一个WhereSelectEnumerableIterator 。 这是一个实现IEnumerable的内部.Net类, 而不是IQueryable 。 所以转换到另一种types已经发生,随后的AsQueryable不能再返回原始的源。

这意味着,使用AsQueryable 不是一个神奇的方式注入一个查询提供程序的特定function到一个枚举。 假设你这样做

 var query = context.Observations.Select(o => o.Id) .AsEnumerable().Select(x => x.ToString()) .AsQueryable() .Where(...) 

where条件永远不会被翻译成SQL。 AsEnumerable()紧接着LINQ语句明确地切断与entity framework查询提供者的连接。

我特意展示了这个例子,因为我在这里看到了一些问题,例如人们试图通过调用AsQueryable来“注入” Includefunction到一个集合中。 它编译和运行,但它没有做任何事情,因为底层对象不再有一个Include实现。

具体实施

到目前为止,这只是关于Queryable.AsQueryableEnumerable.AsEnumerable扩展方法。 但是,当然,任何人都可以使用相同的名称(和函数)编写实例方法或扩展方法。

实际上,特定的AsEnumerable扩展方法的一个常见示例是DataTableExtensions.AsEnumerableDataTable不实现IQueryableIEnumerable ,所以常规扩展方法不适用。

ToList()

  • 立即执行查询

AsEnumerable()

  • 懒(稍后执行查询)
  • 参数: Func<TSource, bool>
  • 每条logging加载到应用程序内存中,然后处理/过滤它们。 (例如Where / Take / Skip,它会从table1中select*到内存中,然后select第一个X元素)(在这种情况下,它做了什么:Linq-to-SQL + Linq-to-Object)

AsQueryable已()

  • 懒(稍后执行查询)
  • 参数: Expression<Func<TSource, bool>>
  • 将expression式转换为T-SQL(使用特定的提供程序),远程查询并将结果加载到应用程序内存。
  • 这就是为什么DbSet(在entity framework中)也inheritance了IQueryable来获得高效的查询。
  • 不要加载每个logging,例如,如果Take(5),它将在后台生成select顶级5 * SQL。 这意味着这种types对SQL数据库更友好,这就是为什么这种types通常具有更高的性能,并build议在处理数据库时。
  • 所以AsQueryable()通常比AsEnumerable()更快,因为它首先生成T-SQL,其中包括Linq中的所有条件。

ToList()将成为内存中的所有东西,然后你将会在这里工作。 所以,ToList()。其中​​(应用一些filter)在本地执行。 AsQueryable()将远程执行所有事情,即将其filter发送到数据库进行应用。 在执行之前,Queryable不执行任何操作。 ToList,但立即执行。

另外,看看这个答案为什么使用AsQueryable()而不是List()? 。

编辑:另外,在你的情况下,一旦你做了ToList(),那么每个后续的操作是本地包括AsQueryable()。 一旦开始在本地执行,您将无法切换到远程。 希望这个更清楚一点。