【问题标题】:Should I return Task<IEnumerable<T>> or IAsyncEnumerable<T> from repository?我应该从存储库返回 Task<IEnumerable<T>> 还是 IAsyncEnumerable<T>?
【发布时间】:2021-12-12 14:46:19
【问题描述】:

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

public IAsyncEnumerable<Order> GetOrder(int orderId)
{
    return blablabla.AsAsyncEnumerable();
}

public Task<IEnumerable<Order>> GetOrder(int orderId)
{
    return blablabla.ToListAsync();
}

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

【问题讨论】:

标签: asp.net .net asp.net-core .net-core entity-framework-core


【解决方案1】:

根据source.ToListAsync 无论如何都会在内部使用IAsyncEnumerable,因此两者并没有太多的性能优势。 但是.ToListAsync.ToArrayAsync 的一个重要特征是cancellation

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;
}

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

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

【讨论】:

  • 例如,如果您要查找的项目是第一个项目,是否有任何性能优势? asyncEnumerableList.firstOrDefaultAsync() 优于 list.firstOrDefault()?
【解决方案2】:

真正的决定取决于您是要缓冲还是流式传输。

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

来自docs

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

一般来说,最好是流式传输,除非您需要缓冲。

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

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

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


如果从事该项目的团队对流语义不满意,或者如果大多数代码已经缓冲,则使用类似AsStream(例如GetOrdersAsStream())之类的后缀可能是有意义的他们知道他们不应该多次列举它。

所以一个存储库可以有:

async Task<List<Order>> GetOrders() => await GetOrdersAsStream.ToListAsync();
IAsyncEnumerable<Order> GetOrdersAsStream() => ...

【讨论】:

  • AsEnumerable () 推迟执行,直到它开始枚举结果。因此,第一个示例中的方法(具有 IAsyncEnumerable 返回类型)不会在存储库中实现查询
  • @JanKowalski 我明白了。我已经更新了答案。当我们知道数据是否正在流式传输(从而导致每次枚举都发生 DB 命中)或数据是否被缓冲时,它是否命中存储库中的 DB 并不重要。
猜你喜欢
  • 1970-01-01
  • 2011-03-03
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-03-05
  • 1970-01-01
相关资源
最近更新 更多