What's the difference(s) between .ToList(), .AsEnumerable(), AsQueryable()?
我知道linq与实体和linq与对象的一些区别,前者实现
我的问题是这三种方法的技术差异是什么?我看到在很多情况下,它们都能工作。我还看到使用它们的组合,如
这些方法到底是什么意思?
是否存在性能问题或导致使用一个而另一个的问题?
例如,为什么要使用
关于这个有很多话要说。让我把重点放在
当源对象已经实现目标接口时,将返回源对象本身,但将其强制转换到目标接口。换句话说:类型没有更改,但编译时类型是。好的。
当源对象不实现目标接口时,源对象将转换为实现目标接口的对象。因此类型和编译时类型都会更改。好的。
让我用一些例子来说明这一点。我有一个报告编译时类型和对象实际类型的小方法(由jon skeet提供):好的。
1 2 3 4 5 | 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
1 2 3 | ReportTypeProperties(context.Observations); ReportTypeProperties(context.Observations.AsEnumerable()); ReportTypeProperties(context.Observations.AsQueryable()); |
结果:好的。
1 2 3 4 5 6 7 8 | 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 |
您可以看到,表类本身总是返回的,但是它的表示形式会改变。好的。
现在一个实现
1 2 3 4 | var ints = new[] { 1, 2 }; ReportTypeProperties(ints); ReportTypeProperties(ints.AsEnumerable()); ReportTypeProperties(ints.AsQueryable()); |
结果:好的。
1 2 3 4 5 6 7 8 | Compile-time type: Int32[] Actual type: Int32[] Compile-time type: IEnumerable`1 Actual type: Int32[] Compile-time type: IQueryable`1 Actual type: EnumerableQuery`1 |
就在那里。
例如,在实体框架查询中,我们只能使用有限数量的方法。因此,例如,如果我们需要在查询中使用我们自己的方法之一,我们通常会编写好的。
1 2 | var query = context.Observations.Select(o => o.Id) .AsEnumerable().Select(x => MySuperSmartMethod(x)) |
另一方面,当需要强制执行LINQ查询时,
在许多堆栈溢出答案中,我看到人们应用
1 2 3 | context.MyLongWideTable // A table with many records and columns .Where(x => x.Type =="type") .Select(x => new { x.Name, x.CreateDate }) |
…所有内容都被巧妙地转换成一个SQL语句,用于过滤(
现在假设用户只想看到
1 | .Select(x => new { x.Name, x.CreateDate.Date }) |
…不受支持(在写入时)。啊,幸运的是,有一个
1 2 3 | context.MyLongWideTable.AsEnumerable() .Where(x => x.Type =="type") .Select(x => new { x.Name, x.CreateDate.Date }) |
当然,它可能会运行。但它将整个表拉入内存,然后应用过滤器和投影。好吧,大多数人都很聪明,可以先做
1 2 3 | context.MyLongWideTable .Where(x => x.Type =="type").AsEnumerable() .Select(x => new { x.Name, x.CreateDate.Date }) |
但是,仍然首先获取所有列,并在内存中完成投影。好的。
真正的解决办法是:好的。
1 2 3 | context.MyLongWideTable .Where(x => x.Type =="type") .Select(x => new { x.Name, DbFunctions.TruncateTime(x.CreateDate) }) |
(但那只需要多一点知识…)好的。这些方法不能做什么?
现在是一个重要的警告。当你这样做的时候好的。
1 2 | context.Observations.AsEnumerable() .AsQueryable() |
您最终将得到表示为
但是当你这样做的时候好的。
1 2 | context.Observations.AsEnumerable().Select(x => x) .AsQueryable() |
结果会是什么?好的。
这意味着使用
1 2 3 4 | var query = context.Observations.Select(o => o.Id) .AsEnumerable().Select(x => x.ToString()) .AsQueryable() .Where(...) |
Where条件永远不会转换为SQL。后面跟着LINQ语句的
我故意展示这个例子,因为我在这里看到过一些问题,比如人们试图通过调用
到目前为止,这只是关于
事实上,特定
托尔斯特()
- 立即执行查询
Asvestable()
- 懒惰(稍后执行查询)
- 参数:
Func 。 - 将每个记录加载到应用程序内存中,然后处理/过滤它们。(例如,在WHERE/TAKE/SKIP中,它将从表1中选择*进入内存,然后选择前x个元素)(在本例中,它所做的是:linq to sql+linq to object)
ASQualable()
- 懒惰(稍后执行查询)
- 参数:
Expression 。> - 将表达式转换为T-SQL(使用特定的提供程序),远程查询并将结果加载到应用程序内存中。
- 这就是为什么dbset(在实体框架中)也继承iqueryable以获得有效的查询。
- 不要加载每个记录,例如,如果take(5),它将在后台生成select top 5*sql。这意味着这种类型对SQL数据库更友好,这就是为什么这种类型通常具有更高的性能,并且在处理数据库时建议使用这种类型。
- 因此,
AsQueryable() 通常比AsEnumerable() 工作得快得多,因为它首先生成T-SQL,其中包括您的linq中的所有where条件。
tolist()将成为内存中的所有内容,然后您将处理它。所以,tolist()。其中(应用一些过滤器)在本地执行。asqueryable()将远程执行所有操作,即将其上的筛选器发送到数据库进行应用。在执行之前,Queryable不会做任何事情。但会立即执行。
另外,看看这个答案为什么使用asqueryable()而不是list()?.
编辑:另外,在您的情况下,一旦您执行tolist(),那么每个后续操作都是本地的,包括asqueryable()。一旦开始在本地执行,就无法切换到远程。希望这能更清楚一点。