关于这一点有很多话要说。让我重点关注AsEnumerable
并一路AsQueryable
提及。ToList()
这些方法有什么作用?
AsEnumerable
并分别AsQueryable
转换或转换为IEnumerable
or 。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
- 将 a 转换IEnumerable<T>
为 a 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
将永远无法返回原始源。
这意味着 using并不是一种将具有特定功能的查询提供程序神奇地注入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
or IEnumerable
,所以常规的扩展方法不适用。