ToAsyncEnumerable().Single() 与 SingleAsync()
ToAsyncEnumerable().Single() vs SingleAsync()
我正在以独立于 EF-Core 的方式构建和执行我的查询,因此我依赖 IQueryable<T>
来获得所需的抽象级别。我正在用等待的 ToAsyncEnumerable().Single()
呼叫替换等待的 SingleAsync()
呼叫。我还将 ToListAsync()
调用替换为 ToAsyncEnumerable().ToList()
调用。但我只是偶然发现了 ToAsyncEnumerable()
方法,所以我不确定我是否正确使用了它。
为了阐明我指的是哪些扩展方法,它们的定义如下:
SingleAsync
和 ToListAsync
在 Microsoft.EntityFrameworkCore
命名空间和程序集中的 EntityFrameworkQueryableExtensions
class 上定义。
ToAsyncEnumerable
在 System.Interactive.Async
程序集的 System.Linq
命名空间中的 AsyncEnumerable
class 上定义。
当查询针对 EF-Core 运行时,调用 ToAsyncEnumerable().Single()/ToList()
与 SingleAsync()/ToListAsync()
在功能和性能上是否相同?如果不是那么它们有何不同?
我看了一眼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 运算符。
对于返回序列的方法(如 ToListAsync
、ToArrayAsync
)我不希望有什么不同。
但是对于单值返回方法(First
、FirstOrDefault
、Single
、Min
、Max
、[=17= 的异步版本]等)肯定会有区别。这与在 IQueryable<T>
与 IEnumerable<T>
上执行这些方法的区别相同。在前一种情况下,它们由数据库查询处理,将单个值返回给客户端,而在后一种情况下,整个结果集将返回给客户端并在内存中处理。
所以,虽然一般来说抽象 EF Core 的想法是好的,但它会导致 IQueryable<T>
的性能问题,因为可查询的异步处理没有标准化,并且转换为 IEnumerable<T>
会改变执行上下文,因此实现单值返回 LINQ 方法。
P.S。通过标准化,我的意思如下。 IQueryable
的同步处理由IQueryProvider
提供(standard interface from System.Linq
namespace in System.Core.dll
assembly)Execute
方法。异步处理需要引入另一个类似于 EF Core custom IAsyncQueryProvider
的 standard 接口(在 Microsoft.EntityFrameworkCore.Query.Internal
命名空间内 Microsoft.EntityFrameworkCore.dll
组装)。我想这需要 BCL 团队 cooperation/approval 并且需要时间,这就是他们决定暂时采用自定义路径的原因。
当原始来源是 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() );
}
根据 EF Core 的 Microsoft 官方文档(所有版本,包括当前的 2.1 版本):
This API supports the Entity Framework Core infrastructure and is not intended to be used directly from your code. This API may change or be removed in future releases.
p.s。我个人发现它与 AutoMapper 工具结合使用时存在问题(至少,直到版本 6.2.2) - 它只是不映射 IAsyncEnumerable 类型的集合(与 IEnumerable 不同,AutoMapper 可以无缝地使用它)。
我正在以独立于 EF-Core 的方式构建和执行我的查询,因此我依赖 IQueryable<T>
来获得所需的抽象级别。我正在用等待的 ToAsyncEnumerable().Single()
呼叫替换等待的 SingleAsync()
呼叫。我还将 ToListAsync()
调用替换为 ToAsyncEnumerable().ToList()
调用。但我只是偶然发现了 ToAsyncEnumerable()
方法,所以我不确定我是否正确使用了它。
为了阐明我指的是哪些扩展方法,它们的定义如下:
SingleAsync
和ToListAsync
在Microsoft.EntityFrameworkCore
命名空间和程序集中的EntityFrameworkQueryableExtensions
class 上定义。ToAsyncEnumerable
在System.Interactive.Async
程序集的System.Linq
命名空间中的AsyncEnumerable
class 上定义。
当查询针对 EF-Core 运行时,调用 ToAsyncEnumerable().Single()/ToList()
与 SingleAsync()/ToListAsync()
在功能和性能上是否相同?如果不是那么它们有何不同?
我看了一眼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 运算符。
对于返回序列的方法(如 ToListAsync
、ToArrayAsync
)我不希望有什么不同。
但是对于单值返回方法(First
、FirstOrDefault
、Single
、Min
、Max
、[=17= 的异步版本]等)肯定会有区别。这与在 IQueryable<T>
与 IEnumerable<T>
上执行这些方法的区别相同。在前一种情况下,它们由数据库查询处理,将单个值返回给客户端,而在后一种情况下,整个结果集将返回给客户端并在内存中处理。
所以,虽然一般来说抽象 EF Core 的想法是好的,但它会导致 IQueryable<T>
的性能问题,因为可查询的异步处理没有标准化,并且转换为 IEnumerable<T>
会改变执行上下文,因此实现单值返回 LINQ 方法。
P.S。通过标准化,我的意思如下。 IQueryable
的同步处理由IQueryProvider
提供(standard interface from System.Linq
namespace in System.Core.dll
assembly)Execute
方法。异步处理需要引入另一个类似于 EF Core custom IAsyncQueryProvider
的 standard 接口(在 Microsoft.EntityFrameworkCore.Query.Internal
命名空间内 Microsoft.EntityFrameworkCore.dll
组装)。我想这需要 BCL 团队 cooperation/approval 并且需要时间,这就是他们决定暂时采用自定义路径的原因。
当原始来源是 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() );
}
根据 EF Core 的 Microsoft 官方文档(所有版本,包括当前的 2.1 版本):
This API supports the Entity Framework Core infrastructure and is not intended to be used directly from your code. This API may change or be removed in future releases.
p.s。我个人发现它与 AutoMapper 工具结合使用时存在问题(至少,直到版本 6.2.2) - 它只是不映射 IAsyncEnumerable 类型的集合(与 IEnumerable 不同,AutoMapper 可以无缝地使用它)。