我以独立于EF-Core的方式构建和执行查询,因此我依靠IQueryable<T>
来获得所需的抽象级别。我已等待SingleAsync()
来电等待ToAsyncEnumerable().Single()
来电。我还使用ToListAsync()
来电取代ToAsyncEnumerable().ToList()
来电。但我刚刚采用ToAsyncEnumerable()
方法,所以我不确定我是否正确使用它。
为了澄清我所指的扩展方法,它们的定义如下:
SingleAsync
和ToListAsync
在EntityFrameworkQueryableExtensions
命名空间和程序集中的Microsoft.EntityFrameworkCore
类上定义。
ToAsyncEnumerable
在AsyncEnumerable
程序集的System.Linq
命名空间中的System.Interactive.Async
类上定义。当查询针对EF-Core运行时,调用ToAsyncEnumerable().Single()/ToList()
与SingleAsync()/ToListAsync()
的函数和性能是否等效?如果没有,那么它们有何区别?
答案 0 :(得分:5)
对于返回序列的方法(如ToListAsync
,ToArrayAsync
),我不希望有所不同。
但是对于单值返回方法(First
,FirstOrDefault
,Single
,Min
,Max
,Sum
等的异步版本。)肯定会有所不同。通过在IQueryable<T>
vs IEnumerable<T>
上执行这些方法,它与差异相同。在前一种情况下,它们由数据库查询处理,向客户端返回单个值,而在稍后,整个结果集将返回到客户端并在内存中处理。
因此,虽然一般来说抽象EF Core的想法很好,但它会导致IQueryable<T>
的性能问题,因为可查询的异步处理不是标准化的,转换为IEnumerable<T>
会改变执行上下文,因此实现单值返回LINQ方法。
P.S。通过标准化我的意思是以下。 IQueryable
的同步处理由IQueryProvider
(System.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可能会在将来的版本中更改或删除。
P.S。我个人发现它与AutoMapper工具相结合是有问题的(至少在版本6.2.2之前) - 它不会映射IAsyncEnumerable类型的集合(与IEnumerable不同,AutoMapper可以无缝地工作)。