.ToList(),. AsEnumerable(),AsQueryable()之间的区别是什么?

Ami*_*aqi 166 linq-to-entities entity-framework entity-framework-4 entity-framework-5

我知道LINQ to Entities和LINQ to Objects的一些区别,第一个实现IQueryable和第二个实现IEnumerable,我的问题范围在EF 5中.

我的问题是这3种方法的技术差异是什么?我看到,在许多情况下,所有这些都有效.我也看到使用它们的组合.ToList().AsQueryable().

  1. 这些方法究竟意味着什么?

  2. 是否有任何性能问题或某些因素会导致使用另一个?

  3. 例如,为什么会使用?.ToList().AsQueryable()而不是.AsQueryable()

Ger*_*old 329

关于这一点有很多话要说.让我专注于AsEnumerableAsQueryable和提ToList()沿途.

这些方法有什么作用?

AsEnumerable并分别AsQueryable转换或转换为IEnumerableIQueryable.我说演员或转换有一个原因:

  • 当源对象已实现目标接口时,将返回源对象本身,但将其强制转换为目标接口.换句话说:类型没有改变,但编译时类型是.

  • 当源对象未实现目标接口时,源对象将转换为实现目标接口的对象.因此,类型和编译时类型都会更改.

让我用一些例子来说明这一点.我有这个小方法来报告编译时类型和对象的实际类型(由Jon Skeet提供):

void ReportTypeProperties<T>(T obj)
{
    Console.WriteLine("Compile-time type: {0}", typeof(T).Name);
    Console.WriteLine("Actual type: {0}", obj.GetType().Name);
}
Run Code Online (Sandbox Code Playgroud)

让我们尝试一个任意的linq-to-sql Table<T>,它实现IQueryable:

ReportTypeProperties(context.Observations);
ReportTypeProperties(context.Observations.AsEnumerable());
ReportTypeProperties(context.Observations.AsQueryable());
Run Code Online (Sandbox Code Playgroud)

结果:

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
Run Code Online (Sandbox Code Playgroud)

您会看到始终返回表类本身,但其表示会更改.

现在是一个实现的对象IEnumerable,而不是IQueryable:

var ints = new[] { 1, 2 };
ReportTypeProperties(ints);
ReportTypeProperties(ints.AsEnumerable());
ReportTypeProperties(ints.AsQueryable());
Run Code Online (Sandbox Code Playgroud)

结果:

Compile-time type: Int32[]
Actual type: Int32[]

Compile-time type: IEnumerable`1
Actual type: Int32[]

Compile-time type: IQueryable`1
Actual type: EnumerableQuery`1
Run Code Online (Sandbox Code Playgroud)

它就是.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))
Run Code Online (Sandbox Code Playgroud)

ToList - 将IEnumerable<T>a 转换为a List<T>- 通常也用于此目的.使用AsEnumerablevs. 的优点ToListAsEnumerable不执行查询.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 })
Run Code Online (Sandbox Code Playgroud)

...一切都被巧妙地翻译成了一个过滤(Where)和项目(Select)的SQL语句.也就是说,SQL结果集的长度和宽度都会减少.

现在假设用户只想查看日期部分CreateDate.在实体框架中,您将很快发现......

.Select(x => new { x.Name, x.CreateDate.Date })
Run Code Online (Sandbox Code Playgroud)

...不受支持(在撰写本文时).啊,幸运的是有AsEnumerable修复:

context.MyLongWideTable.AsEnumerable()
       .Where(x => x.Type == "type")
       .Select(x => new { x.Name, x.CreateDate.Date })
Run Code Online (Sandbox Code Playgroud)

当然,它可能会运行.但它将整个表拉入内存然后应用过滤器和投影.嗯,大多数人都很聪明,可以做第Where一个:

context.MyLongWideTable
       .Where(x => x.Type == "type").AsEnumerable()
       .Select(x => new { x.Name, x.CreateDate.Date })
Run Code Online (Sandbox Code Playgroud)

但仍然首先获取所有列,并在内存中完成投影.

真正的解决方法是:

context.MyLongWideTable
       .Where(x => x.Type == "type")
       .Select(x => new { x.Name, DbFunctions.TruncateTime(x.CreateDate) })
Run Code Online (Sandbox Code Playgroud)

(但这需要更多的知识......)

这些方法没有做什么?

现在一个重要的警告.当你这样做

context.Observations.AsEnumerable()
                    .AsQueryable()
Run Code Online (Sandbox Code Playgroud)

你最终得到的源对象表示为IQueryable.(因为这两种方法只能转换并且不转换).

但是,当你这样做

context.Observations.AsEnumerable().Select(x => x)
                    .AsQueryable()
Run Code Online (Sandbox Code Playgroud)

结果会是什么?

Select产生WhereSelectEnumerableIterator.这是一个实现的内部.Net类IEnumerable,而不是IQueryable.因此,转换到另一种类型已经发生,随后AsQueryable再也无法返回原始来源.

这个含义是,使用AsQueryable没有办法神奇地注入查询提供其具体特点为枚举.假设你这样做

var query = context.Observations.Select(o => o.Id)
                   .AsEnumerable().Select(x => x.ToString())
                   .AsQueryable()
                   .Where(...)
Run Code Online (Sandbox Code Playgroud)

where条件永远不会转换为SQL.AsEnumerable()其次是LINQ语句,最终切断了与实体框架查询提供程序的连接.

我故意展示这个例子,因为我在这里看到了一些问题,例如人们试图Include通过调用将"注入" 功能集成到集合中AsQueryable.它编译并运行,但它什么都不做,因为底层对象不再具有Include实现.

具体实施

到目前为止,这只是关于Queryable.AsQueryableEnumerable.AsEnumerable扩展方法.但是当然任何人都可以使用相同的名称(和函数)编写实例方法或扩展方法.

实际上,特定AsEnumerable扩展方法的一个常见示例是DataTableExtensions.AsEnumerable.DataTable不执行IQueryableIEnumerable,因此正扩展方法不适用.


Xin*_*Xin 40

ToList()

  • 立即执行查询

AsEnumerable()

  • 懒惰(稍后执行查询)
  • 参数: Func<TSource, bool>
  • 每条记录加载到应用程序内存中,然后处理/过滤它们.(例如Where/Take/Skip,它将从table1中选择*进入内存,然后选择前X个元素)(在这种情况下,它做了什么:Linq-to-SQL + Linq-to-Object)

AsQueryable已()

  • 懒惰(稍后执行查询)
  • 参数: Expression<Func<TSource, bool>>
  • 将Expression转换为T-SQL(使用特定提供程序),远程查询并将结果加载到应用程序内存中.
  • 这就是DbSet(在Entity Framework中)也继承IQueryable以获得高效查询的原因.
  • 不要加载每条记录,例如如果Take(5),它将在后台生成select top 5*SQL.这意味着这种类型对SQL数据库更友好,这就是为什么这种类型通常具有更高的性能,并且在处理数据库时建议使用.
  • 因此AsQueryable()通常比AsEnumerable()最初生成T-SQL 要快得多,其中包括Linq中的所有where条件.


ash*_*ina 12

ToList()将成为内存中的所有内容,然后您将继续处理它.所以,ToList().where(应用一些过滤器)在本地执行.AsQueryable()将远程执行所有操作,即将其上的过滤器发送到数据库以进行应用.在您执行它之前,Queryable不会执行任何操作.然而,ToList立即执行.

另外,看看这个答案为什么使用AsQueryable()而不是List()?.

编辑:此外,在你的情况下,一旦你做ToList()然后每个后续操作是本地的,包括AsQueryable().一旦开始本地执行,就无法切换到远程.希望这会让它更清晰一些.

  • "AsQueryable()将远程执行所有内容"仅当可枚举的内容已经可查询时.否则,这是不可能的,所有*仍*在本地运行.问题有"...... ToList().AsQueryable()",它可以在你的答案中使用一些澄清,IMO. (2认同)