ToAsyncEnumerable()。Single()vs SingleAsync()

时间:2016-11-12 08:15:25

标签: entity-framework linq system.reactive entity-framework-core .net-core

我以独立于EF-Core的方式构建和执行查询,因此我依靠IQueryable<T>来获得所需的抽象级别。我已等待SingleAsync()来电等待ToAsyncEnumerable().Single()来电。我还使用ToListAsync()来电取代ToAsyncEnumerable().ToList()来电。但我刚刚采用ToAsyncEnumerable()方法,所以我不确定我是否正确使用它。

为了澄清我所指的扩展方法,它们的定义如下:

    {li> SingleAsyncToListAsyncEntityFrameworkQueryableExtensions命名空间和程序集中的Microsoft.EntityFrameworkCore类上定义。
  • ToAsyncEnumerableAsyncEnumerable程序集的System.Linq命名空间中的System.Interactive.Async类上定义。

当查询针对EF-Core运行时,调用ToAsyncEnumerable().Single()/ToList()SingleAsync()/ToListAsync()的函数和性能是否等效?如果没有,那么它们有何区别?

4 个答案:

答案 0 :(得分:5)

对于返回序列的方法(如ToListAsyncToArrayAsync),我不希望有所不同。

但是对于单值返回方法(FirstFirstOrDefaultSingleMinMaxSum等的异步版本。)肯定会有所不同。通过在IQueryable<T> vs IEnumerable<T>上执行这些方法,它与差异相同。在前一种情况下,它们由数据库查询处理,向客户端返回单个值,而在稍后,整个结果集将返回到客户端并在内存中处理。

因此,虽然一般来说抽象EF Core的想法很好,但它会导致IQueryable<T>的性能问题,因为可查询的异步处理不是标准化的,转换为IEnumerable<T>会改变执行上下文,因此实现单值返回LINQ方法。

P.S。通过标准化我的意思是以下。 IQueryable的同步处理由IQueryProviderSystem.Linq程序集中System.Core.dll命名空间中的标准接口提供。Execute方法。异步处理需要引入类似于EF Core 自定义 IAsyncQueryProvider的另一个标准界面(Microsoft.EntityFrameworkCore.Query.Internal程序集中的Microsoft.EntityFrameworkCore.dll名称空间内)。我想这需要BCL团队的合作/批准,并且需要时间,这就是为什么他们现在决定采用自定义路径。

答案 1 :(得分:1)

当原始来源为DbSet时,ToAsyncEnumerable().Single()在数据库包含多个匹配行的例外情况下与SingleAsync()的性能不同。但是在更可能的情况下,你们只期望并且只收到一行,它就是一样的。比较生成的SQL:

SingleAsync():
    SELECT TOP(2) [l].[ID]
    FROM [Ls] AS [l]

ToAsyncEnumerable().Single():
    SELECT [l].[ID]
    FROM [Ls] AS [l]

ToAsyncEnumerable()打破IQueryable调用链并进入LINQ-to-Objects域。任何下游过滤都发生在内存中。您可以通过上游过滤来缓解此问题。所以而不是:

ToAsyncEnumerable().Single( l => l.Something == "foo" ):
    SELECT [l].[ID], [l].[Something]
    FROM [Ls] AS [l]

你可以这样做:

Where( l => l.Something == "foo" ).ToAsyncEnumerable().Single():
    SELECT [l].[ID], [l].[Something]
    FROM [Ls] AS [l]
    WHERE [l].[Something] = N'foo'

如果这种方法仍然让你感到不安,那么可以考虑定义像这样的扩展方法:

using System.Linq;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Query.Internal;

static class Extensions
{
    public static Task<T> SingleAsync<T>( this IQueryable<T> source ) =>
        source.Provider is IAsyncQueryProvider
            ? EntityFrameworkQueryableExtensions.SingleAsync( source )
            : Task.FromResult( source.Single() );
}

答案 2 :(得分:0)

我看了Single的源代码(第90行) 它清楚地说明了枚举器只提前一次(为了成功的操作)。

        using (var e = source.GetEnumerator())
        {
            if (!await e.MoveNext(cancellationToken)
                        .ConfigureAwait(false))
            {
                throw new InvalidOperationException(Strings.NO_ELEMENTS);
            }
            var result = e.Current;
            if (await e.MoveNext(cancellationToken)
                       .ConfigureAwait(false))
            {
                throw new InvalidOperationException(Strings.MORE_THAN_ONE_ELEMENT);
            }
            return result;
        }

由于这种实现方式与现在一样好(现在),可以肯定地说使用Ix单一运算符不会损害性能。

对于SingleAsync,你可以确定它是以类似的方式实现的,即使它不是(这是值得怀疑的),它也不能胜过Ix Single运算符。

答案 3 :(得分:0)

根据EF Core的官方Microsoft文档(所有版本,包括当前的2.1版本):

  

此API支持Entity Framework Core基础结构,不能直接在您的代码中使用。此API可能会在将来的版本中更改或删除。

来源:https://docs.microsoft.com/en-us/dotnet/api/microsoft.entityframeworkcore.query.internal.asynclinqoperatorprovider.toasyncenumerable?view=efcore-2.1

P.S。我个人发现它与AutoMapper工具相结合是有问题的(至少在版本6.2.2之前) - 它不会映射IAsyncEnumerable类型的集合(与IEnumerable不同,AutoMapper可以无缝地工作)。