我知道LINQ to Entities和LINQ to Objects的一些区别,第一个实现IQueryable
,第二个实现IEnumerable
,我的问题范围在EF 5中。
我的问题是这3种方法的技术差异是什么?我看到,在许多情况下,所有这些都有效。我也看到使用它们的组合,如.ToList().AsQueryable()
。
这些方法究竟意味着什么?
是否有任何性能问题导致使用其中一个?
为什么会使用.ToList().AsQueryable()
代替.AsQueryable()
?
答案 0 :(得分:306)
关于这一点有很多话要说。在此过程中,让我关注AsEnumerable
和AsQueryable
并提及ToList()
。
AsEnumerable
和AsQueryable
分别转换或转换为IEnumerable
或IQueryable
。我说强制转换或转换的原因是:
当源对象已实现目标接口时,将返回源对象本身,但强制转换到目标接口。换句话说:类型没有改变,但编译时类型是。
当源对象未实现目标接口时,源对象转换到实现目标接口的对象中。因此,类型和编译时类型都会更改。
让我用一些例子来说明这一点。我有这个小方法来报告编译时类型和对象的实际类型(courtesy Jon Skeet):
void ReportTypeProperties<T>(T obj)
{
Console.WriteLine("Compile-time type: {0}", typeof(T).Name);
Console.WriteLine("Actual type: {0}", obj.GetType().Name);
}
让我们尝试一个实现Table<T>
的任意linq-to-sql 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
,其中&#34;代表IEnumerable<T>
集合作为IQueryable<T>
数据源。&#34; (MSDN)。
AsEnumerable
经常用于从任何IQueryable
实现切换到LINQ到对象(L2O),主要是因为前者不支持L2O所具有的功能。有关详细信息,请参阅What is the effect of AsEnumerable() on a LINQ Entity?。
例如,在实体框架查询中,我们只能使用有限数量的方法。因此,例如,如果我们需要在查询中使用我们自己的方法之一,我们通常会编写类似
的内容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语句中创建可枚举集合接受表达式。有关详情,请参阅此处:Do i really need use AsQueryable() on collection?。
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) })
(但这需要更多的知识......)
现在是一个重要的警告。当你这样做
context.Observations.AsEnumerable()
.AsQueryable()
您最终会得到表示为IQueryable
的源对象。 (因为这两种方法只能投射并且不能转换)。
但是当你做的时候
context.Observations.AsEnumerable().Select(x => x)
.AsQueryable()
结果会是什么?
Select
生成WhereSelectEnumerableIterator
。这是一个实现IEnumerable
,而不是IQueryable
的内部.Net类。因此,转换为其他类型已经发生,后续的AsQueryable
永远不会再返回原始来源。
这意味着使用AsQueryable
并不是一种神奇地将查询提供程序及其特定功能注入可枚举的方法。假设你做了
var query = context.Observations.Select(o => o.Id)
.AsEnumerable().Select(x => x.ToString())
.AsQueryable()
.Where(...)
where条件永远不会转换为SQL。 AsEnumerable()
后跟LINQ语句明确地切断了与实体框架查询提供程序的连接。
我刻意展示这个例子,因为我在这里看到了一些问题,例如人们试图注入&#39;通过调用Include
将AsQueryable
个功能添加到集合中。它编译并运行,但它什么都不做,因为底层对象不再有Include
实现。
到目前为止,这只涉及Queryable.AsQueryable
和Enumerable.AsEnumerable
扩展方法。但是当然任何人都可以使用相同的名称(和函数)编写实例方法或扩展方法。
事实上,特定AsEnumerable
扩展方法的常见示例是DataTableExtensions.AsEnumerable
。 DataTable
未实现IQueryable
或IEnumerable
,因此常规扩展方法不适用。
答案 1 :(得分:35)
<强> ToList()强>
<强> AsEnumerable()强>
Func<TSource, bool>
<强> AsQueryable已()强>
Expression<Func<TSource, bool>>
AsQueryable()
通常比AsEnumerable()
工作得更快,因为它首先会生成T-SQL,其中包含Linq中的所有where条件。答案 2 :(得分:11)
ToList()将成为内存中的所有内容,然后您将继续处理它。 所以,ToList()。where(应用一些过滤器)在本地执行。 AsQueryable()将远程执行所有操作,即将其上的过滤器发送到数据库以进行应用。 在您执行它之前,Queryable不会执行任何操作。但是,ToList会立即执行。
另外,请看这个答案Why use AsQueryable() instead of List()?。
编辑: 此外,在您的情况下,一旦您执行ToList(),则每个后续操作都是本地的,包括AsQueryable()。一旦开始本地执行,就无法切换到远程。 希望这会让它更清晰一些。
答案 3 :(得分:0)
在以下代码上遇到了不良的性能。
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那样使用。