关于这个有很多话要说。让我专注于AsEnumerable
并一路AsQueryable
提及ToList()
。
这些方法是做什么的?
AsEnumerable
和AsQueryable
投或转换到IEnumerable
或IQueryable
分别。我说投放或转换是有原因的:
让我用一些例子来说明这一点。我有这个小方法可以报告对象的编译时类型和实际类型(由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
,表示“将IEnumerable<T>
集合表示为IQueryable<T>
数据源”。(MSDN)。
什么用途?
AsEnumerable
通常用于从任何IQueryable
实现到LINQ 切换到对象(L2O),主要是因为前者不支持L2O所具有的功能。有关更多详细信息,请参见AsEnumerable()对LINQ实体有什么影响?。
例如,在实体框架查询中,我们只能使用数量有限的方法。因此,例如,如果我们需要在查询中使用我们自己的方法之一,我们通常会编写类似
var query = context.Observations.Select(o => o.Id)
.AsEnumerable().Select(x => MySuperSmartMethod(x))
ToList
–将an转换IEnumerable<T>
为List<T>
–通常也用于此目的。使用AsEnumerable
vs. 的优点ToList
是AsEnumerable
不会执行查询。AsEnumerable
保留延迟执行,并且不构建通常无用的中间列表。
另一方面,当需要强制执行LINQ查询时,ToList
可以这样做。
AsQueryable
可用于使可枚举的集合接受LINQ语句中的表达式。请参阅此处以获取更多详细信息:我真的需要在集合上使用AsQueryable()吗?。
注意滥用毒品!
AsEnumerable
像毒品一样工作。这是一个快速解决方案,但要付出一定的代价,并且无法解决潜在的问题。
在许多Stack Overflow答案中,我看到人们正在申请AsEnumerable
解决LINQ表达式中不受支持的方法的任何问题。但是价格并不总是很明确。例如,如果您这样做:
context.MyLongWideTable // A table with many records and columns
.Where(x => x.Type == "type")
.Select(x => new { x.Name, x.CreateDate })
...所有内容都被整齐地转换为可过滤(Where
)和项目(Select
)的SQL语句。也就是说,分别减少了SQL结果集的长度和宽度。
现在,假设用户只想查看中的日期部分CreateDate
。在实体框架中,您将很快发现...
.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 })
当然,它可能会运行。但是它将整个表拉入内存,然后应用过滤器和投影。好吧,大多数人都很聪明,可以做第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) })
(但这只需要更多知识...)
这些方法不做什么?
恢复IQueryable功能
现在是一个重要的警告。当你做
context.Observations.AsEnumerable()
.AsQueryable()
您将最终得到表示为的源对象IQueryable
。(因为这两种方法都只能转换而不转换)。
但是当你这样做
context.Observations.AsEnumerable().Select(x => x)
.AsQueryable()
结果将是什么?
的Select
产生WhereSelectEnumerableIterator
。这是一个内部.NET类实现IEnumerable
,不是IQueryable
。因此,已经发生了向另一种类型的转换,并且随后的类型AsQueryable
再也无法返回原始源了。
这个含义是,使用AsQueryable
是没有办法神奇地注入查询提供其具体特点为枚举。假设你做
var query = context.Observations.Select(o => o.Id)
.AsEnumerable().Select(x => x.ToString())
.AsQueryable()
.Where(...)
where条件永远不会转换为SQL。AsEnumerable()
紧随其后的LINQ语句将最终切断与实体框架查询提供程序的连接。
我故意显示此示例,因为在这里我看到了一些问题,例如人们尝试Include
通过调用将功能“注入” 到集合中AsQueryable
。它可以编译并运行,但是它什么也不做,因为基础对象不再具有Include
实现。
执行
两个AsQueryable
和AsEnumerable
不执行(或枚举)源对象。他们仅更改其类型或表示。这两个涉及的接口IQueryable
和IEnumerable
,都不过是“等待发生的枚举”。它们在被迫执行之前没有执行,例如,如上所述,通过调用ToList()
。
这意味着,执行一个IEnumerable
通过调用获得AsEnumerable
一个上IQueryable
对象,将执行的底层IQueryable
。后续执行IEnumerable
将再次执行IQueryable
。这可能是非常昂贵的。
具体实施
到目前为止,这仅与Queryable.AsQueryable
和Enumerable.AsEnumerable
扩展方法有关。但是,当然任何人都可以使用相同的名称(和函数)编写实例方法或扩展方法。
实际上,特定AsEnumerable
扩展方法的常见示例是DataTableExtensions.AsEnumerable
。DataTable
没有实现IQueryable
或IEnumerable
,因此常规扩展方法不适用。