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

时间:2013-07-31 10:56:53

标签: entity-framework entity-framework-4 linq-to-entities entity-framework-5

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

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

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

  2. 是否有任何性能问题导致使用其中一个?

  3. 为什么会使用.ToList().AsQueryable()代替.AsQueryable()

4 个答案:

答案 0 :(得分:306)

关于这一点有很多话要说。在此过程中,让我关注AsEnumerableAsQueryable并提及ToList()

这些方法有什么作用?

AsEnumerableAsQueryable分别转换或转换为IEnumerableIQueryable。我说强制转换或转换的原因是:

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

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

让我用一些例子来说明这一点。我有这个小方法来报告编译时类型和对象的实际类型(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> - 通常也用于此目的。使用AsEnumerableToList的优势在于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;通过调用IncludeAsQueryable个功能添加到集合中。它编译并运行,但它什么都不做,因为底层对象不再有Include实现。

具体实施

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

事实上,特定AsEnumerable扩展方法的常见示例是DataTableExtensions.AsEnumerableDataTable未实现IQueryableIEnumerable,因此常规扩展方法不适用。

答案 1 :(得分:35)

<强> ToList()

  • 立即执行查询

<强> AsEnumerable()

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

<强> AsQueryable已()

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