我应该从存储库返回 Task<IEnumerable<T>> 或 IAsyncEnumerable<T> 吗?

Jan*_*ski 14 .net asp.net entity-framework-core .net-core asp.net-core

EF Core 中存储库实现的正确方法是什么?

public IAsyncEnumerable<Order> GetOrder(int orderId)
{
    return blablabla.AsAsyncEnumerable();
}
Run Code Online (Sandbox Code Playgroud)

或者

public Task<IEnumerable<Order>> GetOrder(int orderId)
{
    return blablabla.ToListAsync();
}
Run Code Online (Sandbox Code Playgroud)

打电话是否明智AsAsyncEnumerable()?这种方法安全吗?一方面它不会创建List<T>对象,所以它应该稍微快一些。但从订单角度来看,查询并未具体化,因此我们推迟了 SQL 执行,同时结果可能会发生变化。

And*_*nko 7

根据消息来源 .ToListAsync,无论如何都会IAsyncEnumerable在内部使用,因此其中之一并没有太多的性能优势。但.ToListAsyncor的一个重要特征.ToArrayAsync取消

public static async Task<List<TSource>> ToListAsync<TSource>(
    this IQueryable<TSource> source,
    CancellationToken cancellationToken = default)
{
    var list = new List<TSource>();
    await foreach (var element in source.AsAsyncEnumerable().WithCancellation(cancellationToken))
    {
        list.Add(element);
    }
    return list;
}
Run Code Online (Sandbox Code Playgroud)

列表基本上将所有内容保存在内存中,但只有当列表非常大时,才可能是一个严重的性能问题。在这种情况下,您可以考虑对您的大响应进行寻呼。

public Task<List<Order>> GetOrders(int orderId, int offset, int limit)
{
    return blablabla.Skip(offset).Take(limit).ToListAsync();
}
Run Code Online (Sandbox Code Playgroud)


gld*_*ael 7

这个决定实际上取决于您是否希望缓冲或流式传输。

如果要缓冲结果,请使用ToList()ToListAsync()
如果您想流式传输结果,请使用AsEnumerable()AsAsyncEnumerable()

来自文档

缓冲是指将所有查询结果加载到内存中,而流式传输意味着 EF 每次向应用程序传递一个结果,而不会在内存中包含整个结果集。原则上,流式查询的内存需求是固定的——无论查询返回 1 行还是 1000 行,它们都是相同的;另一方面,缓冲查询返回的行数越多,需要的内存就越多。对于产生大型结果集的查询,这可能是一个重要的性能因素。

一般来说,除非需要缓冲,否则最好进行流式传输。

当您进行流式传输时,一旦读取了数据,就无法在不再次访问数据库的情况下再次读取它。因此,如果您需要多次读取相同的数据,则需要缓冲。

如果存储库流式传输 a IEnumerable,调用者可以选择通过调用ToList()(或ToListAsync()on IAsyncEnumerable) 来缓冲它。如果存储库选择返回 IList,我们就会失去这种灵活性。

因此,要回答您的问题,您最好向存储库传输结果。并让调用者决定是否要缓冲。


如果从事该项目的团队对流语义不熟悉,或者大多数代码已经缓冲,那么在流式传输的方法后面添加类似AsStream(例如GetOrdersAsStream())的后缀可能是有意义的,以便他们知道他们不应该枚举不止一次。

因此,存储库可以具有:

async Task<List<Order>> GetOrders() => await GetOrdersAsStream.ToListAsync();
IAsyncEnumerable<Order> GetOrdersAsStream() => ...
Run Code Online (Sandbox Code Playgroud)