什么是.ToList(),.AsEnumerable(),AsQueryable已()之间的差值(S

2019-05-08 18:09发布

我知道LINQ到实体和LINQ to对象的一些差异,第一实现IQueryable和第二实现IEnumerable ,我的问题范围EF 5中。

我的问题是什么的那些3种方法的技术差异(S)? 我看到,在许多情况下,他们都工作。 我也看到使用它们的组合,像.ToList().AsQueryable()

  1. 什么这些方法的意思是,到底是什么?

  2. 是否有任何性能问题或一些会导致使用一个比其他的?

  3. 为什么人会使用,例如, .ToList().AsQueryable()代替.AsQueryable()

Answer 1:

有很多说这个。 让我专注于AsEnumerableAsQueryable和提ToList()沿途。

什么是这些方法呢?

AsEnumerableAsQueryable投或转换到IEnumerableIQueryable分别。 我说投或转换的原因:

  • 当源对象已经实现了目标接口,源对象本身返回,但转换为目标接口。 换句话说:该类型没有改变,但编译时类型。

  • 当源对象没有实现目标接口,源对象被转换为实现目标接口的对象。 因此,无论类型和编译时类型发生变化。

让我用一些例子说明这一点。 我这有一个报告编译时类型和实际类型的对象(小方法礼貌乔恩斯基特 ):

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

让我们尝试任意LINQ到SQL Table<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到对象(L2O),主要是因为前者不支持L2O具有的功能。 欲了解更多详情,请参阅什么是AsEnumerable()的一个LINQ实体的效果? 。

例如,在一个实体框架的查询,我们只能使用方法个数有限制。 所以,如果,例如,我们需要使用我们自己的方法之一,在查询中,我们通常喜欢写东西

var query = context.Observations.Select(o => o.Id)
                   .AsEnumerable().Select(x => MySuperSmartMethod(x))

ToList -其中转换IEnumerable<T>到一个List<T> -常常用于此目的,以及。 使用的优点AsEnumerableToListAsEnumerable不执行查询。 AsEnumerable保留延迟执行,并且不建立一个经常无用的中间列表。

在另一方面,当LINQ查询的强制执行是需要的, ToList可以是一个办法做到这一点。

AsQueryable可用于制造可枚举集合接受LINQ语句表达。 在这里看到更多的细节: 我是否真的需要在收集使用AsQueryable已()? 。

注意药物滥用!

AsEnumerable就像一个药物。 这是一个快速解决方案,但在成本和它没有解决根本问题。

在许多堆栈溢出的答案,我看到人们应用AsEnumerable修复只是与LINQ表达不支持的方法有任何问题。 但价格并不总是很清楚。 举例来说,如果你这样做:

context.MyLongWideTable // A table with many records and columns
       .Where(x => x.Type == "type")
       .Select(x => new { x.Name, x.CreateDate })

......一切都整齐地翻译成过滤器 (SQL语句Where )和项目Select )。 即,长度和宽度,分别是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 。 这是一个实现内部.NET类IEnumerable ,而不是IQueryable 。 所以转换为另一种类型已经发生和随后AsQueryable不可能再回到原来的来源。

这个含义是,使用AsQueryable 不是办法神奇地注入其具体功能的查询提供到枚举。 假设你做

var query = context.Observations.Select(o => o.Id)
                   .AsEnumerable().Select(x => x.ToString())
                   .AsQueryable()
                   .Where(...)

WHERE条件永远不会被翻译成SQL。 AsEnumerable()然后LINQ语句明确切断与实体框架查询提供商的连接。

我刻意表现,因为我已经看到了问题,在这里,人们例如尝试“注入”这个例子Include通过调用功能集成到一个集合AsQueryable 。 它编译和运行,但不起任何作用,因为潜在的对象没有一个Include实现了。

具体实现

到目前为止,这只是对Queryable.AsQueryableEnumerable.AsEnumerable扩展方法。 但当然,任何人都可以写名称相同(和功能)实例方法或扩展方法。

实际上,特定的一个常见的例子AsEnumerable扩展方法是DataTableExtensions.AsEnumerableDataTable未实现IQueryableIEnumerable ,所以常规的扩展方法不适用。



Answer 2:

ToList()

  • 立即执行查询

AsEnumerable()

  • 懒惰(后来执行查询)
  • 参数: Func<TSource, bool>
  • 加载每个记录到应用程序内存,然后处理/过滤。 (例如在/送/跳跃,它会SELECT * FROM表1,在存储器中,然后选择第一个X元素)(在这种情况下,它的所作所为:LINQ到SQL + LINQ到对象)

AsQueryable已()

  • 懒惰(后来执行查询)
  • 参数: Expression<Func<TSource, bool>>
  • 转换成表达T-SQL(与特定提供商),查询和远程加载结果到应用程序的内存。
  • 这就是为什么DbSet(实体框架)也继承的IQueryable获得高效的查询。
  • 不要装入每个记录,例如,如果采取(5),它会在后台生成SELECT TOP 5 * SQL。 这意味着这种类型是更友好的SQL数据库,这就是为什么这种类型通常具有更高的性能和与数据库打交道时,建议。
  • 所以AsQueryable()通常工作速度远远AsEnumerable()因为它在生成T-SQL第一,其中包括在你的LINQ的所有where条件。


Answer 3:

在内存ToList()将是一切,然后你会努力。 所以,ToList()。其中​​(适用于一些过滤器)本地执行。 AsQueryable已()就可以被发送到数据库的应用将远程执行一切即过滤器。 直到你执行它可查询没有做任何事情。 ToList,但立即执行。

另外,看看这个答案为什么使用AsQueryable已()代替名单()? 。

编辑:另外,你的情况,一旦你做ToList(),然后每一个后续操作是本地的,包括AsQueryable已()。 一旦你开始本地执行不能切换到远程。 希望这使得它一点点清晰。



文章来源: What's the difference(s) between .ToList(), .AsEnumerable(), AsQueryable()?