我知道LINQ到实体和LINQ到对象的一些区别,其中第一个实现了IQueryable,第二个实现了IEnumerable,我的问题范围在EF 5之内。
我的问题是这三种方法在技术上有什么不同?我发现在很多情况下这些方法都有效。我还看到使用它们的组合,如. tolist (). asqueryable()。
这些方法到底是什么意思? 是否存在性能问题或导致使用其中一种而不是另一种的原因? 为什么要使用.ToList().AsQueryable()而不是.AsQueryable()?
我知道LINQ到实体和LINQ到对象的一些区别,其中第一个实现了IQueryable,第二个实现了IEnumerable,我的问题范围在EF 5之内。
我的问题是这三种方法在技术上有什么不同?我发现在很多情况下这些方法都有效。我还看到使用它们的组合,如. tolist (). asqueryable()。
这些方法到底是什么意思? 是否存在性能问题或导致使用其中一种而不是另一种的原因? 为什么要使用.ToList().AsQueryable()而不是.AsQueryable()?
ToList()将是内存中的所有内容,然后您将对其进行处理。 所以,ToList()。其中(应用一些过滤器)在本地执行。 AsQueryable()将远程执行所有内容,即在它上的过滤器被发送到数据库进行应用。 Queryable在执行之前不会做任何事情。但是,ToList会立即执行。
另外,看看这个答案为什么使用AsQueryable()而不是List()?
编辑: 此外,在你的情况下,一旦你执行ToList(),那么所有后续操作都是本地的,包括AsQueryable()。一旦开始本地执行,就不能切换到远程。 希望这能让你们更清楚一点。
关于这个问题有很多可说的。让我把重点放在AsEnumerable和AsQueryable上,顺便提一下ToList()。
这些方法有什么用?
AsEnumerable和AsQueryable分别转换或转换为IEnumerable或IQueryable。我说cast或convert是有原因的:
当源对象已经实现目标接口时,将返回源对象本身,但将其强制转换为目标接口。换句话说:类型没有改变,但是编译时类型改变了。 当源对象没有实现目标接口时,源对象将转换为实现目标接口的对象。所以类型和编译时类型都被改变了。
让我用一些例子来说明。我有这个小方法来报告对象的编译时类型和实际类型(由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表<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 to objects (L2O),主要是因为前者不支持L2O具有的函数。有关更多详细信息,请参阅AsEnumerable()对LINQ实体的影响?
例如,在实体框架查询中,我们只能使用有限数量的方法。例如,如果我们需要在查询中使用我们自己的方法之一,我们通常会写这样的东西
var query = context.Observations.Select(o => o.Id)
.AsEnumerable().Select(x => MySuperSmartMethod(x))
ToList——将IEnumerable<T>转换为List<T>——也经常用于此目的。使用AsEnumerable与使用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语句,最终切断了与实体框架查询提供程序的连接。
我故意展示这个例子是因为我在这里看到过一些问题,例如人们试图通过调用AsQueryable将Include功能“注入”到集合中。它编译并运行,但它什么也不做,因为底层对象不再有Include实现。
执行
AsQueryable和AsEnumerable都不执行(或枚举)源对象。它们只改变它们的类型或表示。两个涉及的接口IQueryable和IEnumerable都只是“等待发生的枚举”。在强制执行之前,它们不会被执行,例如,如上所述,通过调用ToList()。
这意味着执行通过调用IQueryable对象上的AsEnumerable获得的IEnumerable,将执行底层的IQueryable。IEnumerable的后续执行将再次执行IQueryable。这可能非常昂贵。
具体的实现
到目前为止,这只是关于可查询的。AsQueryable和Enumerable。AsEnumerable扩展方法。当然,任何人都可以使用相同的名称(和函数)编写实例方法或扩展方法。
事实上,特定AsEnumerable扩展方法的一个常见示例是DataTableExtensions.AsEnumerable。DataTable没有实现IQueryable或IEnumerable,因此常规扩展方法不适用。
到列表()
立即执行查询
AsEnumerable ()
Lazy(稍后执行查询) 参数:Func<TSource, bool> 将每条记录加载到应用程序内存中,然后处理/过滤它们。(例如,当/Take/Skip时,它将从table1中选择*,进入内存,然后选择前X个元素)(在这种情况下,它所做的是:Linq-to-SQL + Linq-to-Object)
AsQueryable()
lazy (execute the query later) Parameter: Expression<Func<TSource, bool>> Convert Expression into T-SQL (with the specific provider), query remotely and load result to your application memory. That’s why DbSet (in Entity Framework) also inherits IQueryable to get the efficient query. Do not load every record, e.g. if Take(5), it will generate select top 5 * SQL in the background. This means this type is more friendly to SQL Database, and that is why this type usually has higher performance and is recommended when dealing with a database. So AsQueryable() usually works much faster than AsEnumerable() as it generate T-SQL at first, which includes all your where conditions in your Linq.
在下面的代码上遇到了一个糟糕的性能。
void DoSomething<T>(IEnumerable<T> objects){
var single = objects.First(); //load everything into memory before .First()
...
}
固定与
void DoSomething<T>(IEnumerable<T> objects){
T single;
if (objects is IQueryable<T>)
single = objects.AsQueryable().First(); // SELECT TOP (1) ... is used
else
single = objects.First();
}
对于IQueryable,尽可能留在IQueryable中,尽量不要像IEnumerable那样使用。
更新。它可以进一步简化为一个表达,感谢Gert Arnold。
T single = objects is IQueryable<T> q?
q.First():
objects.First();