.ToList(),.AsEnumerable(),AsQueryable()之间有什么区别?
我知道一些LINQ to Entities和LINQ to Objects的区别,第一个实现了IQueryable
,第二个实现了IEnumerable
,我的问题范围在EF 5之内。
我的问题是这三种方法的技术差异是什么? 我看到,在许多情况下,所有这些工作。 我也看到使用它们的组合.ToList().AsQueryable()
。
-
这些方法究竟意味着什么?
-
是否有任何性能问题或会导致相互使用的问题?
-
为什么会使用
.ToList().AsQueryable()
而不是.AsQueryable()
?
对此有很多话要说。 让我关注一下AsEnumerable
和AsQueryable
并提到ToList()
。
这些方法是做什么的?
AsEnumerable
和AsQueryable
分别转换或转换为IEnumerable
或IQueryable
。 我说转换或转换的理由是:
-
当源对象已经实现了目标接口时,源对象本身被返回,但被转换到目标接口。 换句话说: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
也经常用于此目的。 使用AsEnumerable
与ToList
的优点是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
来“注入” Include
function到一个集合中。 它编译和运行,但它没有做任何事情,因为底层对象不再有一个Include
实现。
具体实施
到目前为止,这只是关于Queryable.AsQueryable
和Enumerable.AsEnumerable
扩展方法。 但是,当然,任何人都可以使用相同的名称(和函数)编写实例方法或扩展方法。
实际上,特定的AsEnumerable
扩展方法的一个常见示例是DataTableExtensions.AsEnumerable
。 DataTable
不实现IQueryable
或IEnumerable
,所以常规扩展方法不适用。
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()。 一旦开始在本地执行,您将无法切换到远程。 希望这个更清楚一点。
- 如何在Entity Framework Code First中分离对象?
- entity framework无法加载指定的元数据资源
- 如何在EF中更新父实体时添加/更新子实体
- EF ICollection Vs List Vs IEnumerable Vs IQueryable
- entity framework:当数据库更改时,如何刷新模型?
- LINQ to Entities Exception中不支持指定的types成员“Date”
- 如何在EF Code First中对我的表格进行单数化?
- entity framework如何与recursion层次结构一起工作? 包括()似乎不能使用它
- entity framework与XML文件