【问题标题】:Reuse a IAsyncEnumerable instance without having to iterate again重用 IAsyncEnumerable 实例而无需再次迭代
【发布时间】:2021-06-02 12:42:01
【问题描述】:

我目前正在编写这样的方法:

public async Task<UserModel> GetUserByUserIdAsync(string userId)
{
    IQueryable<UserEntity> usersQuery = BuildQueryable(userId);

    bool any = await usersQuery.ExecuteQuery().AnyAsync();
    
    if (!any) return null; // wanna do other logic in the future

    return (await usersQuery.ExecuteQuery().SingleAsync()).ToUserModel();
}

如您所见,我调用了两次await usersQuery.ExecuteQuery()ExecuteQuery() 是一种迭代我的数据库的方法,可能被认为是一项昂贵的操作。有什么方法可以保存我的IAsyncEnumerable&lt;T&gt;,就像我通常使用IEnumerable&lt;T&gt; 一样,并在我的代码中重复使用它?

我考虑过在上面使用ToListAsync(),但我不确定这是否被认为是好的做法。我还读到我可以返回一个Task&lt;IAsyncEnumerable&lt;T&gt;&gt; 并用它做点什么。处理这个问题的最佳方法是什么?我想实施最有效的解决方案。

【问题讨论】:

  • 自己编写 foreach 怎么样,这样您就可以迭代一次并自己处理所有突发事件?
  • "就像我通常使用 IEnumerable 并在我的代码中重复使用它一样?"你知道这同样适用于IEnumerable&lt;T&gt; 吗?再次迭代相同的IEnumerable&lt;T&gt;当然也会再次获取底层数据存储。你必须以某种方式实现结果,我会选择ToListAsync
  • @HimBromBeere 嗯,是的,但我相信不是在这种情况下。如果我的ExecuteQuery() 返回Task&lt;List&lt;T&gt;&gt;,我可以将列表保存在我的其他方法中,例如var dbResponse = ExecuteQuery();,然后我可以在整个方法中使用 dbResponse,而无需多次访问数据库。
  • @LasseV.Karlsen 你自己写foreach是什么意思?您是指ExecuteQuery() 方法中的foreach 吗?如果是这样,则应该读取给定查询的所有元素。你的意思是我应该用那种方法处理这些情况?
  • 在任何情况下,您至少会访问 DB 一次,因此只需执行 (await usersQuery.ExecuteQuery().FirstOrDefaultAsync()).ToUserModel() 并将其与 null 进行比较以了解特定逻辑

标签: c# asynchronous task-parallel-library ienumerable iasyncenumerable


【解决方案1】:

为什么不直接使用SingleOrDefaultAsync?假设您的实体是一个引用类型,您可以获得您的单个项目,检查它是否为 null 以处理空案例。另一种选择是始终转换enumerable to a list。然后,您可以随意对其进行多次迭代。

【讨论】:

  • 我认为你是对的,我有点想多了。我在想如果我必须多次重复使用该列表,将来我将如何处理它,但如果是这种情况,我可以将它转换为普通列表并使用它。将IAsyncEnumerable&lt;T&gt; 转换为List&lt;T&gt; 有什么缺点吗?另外,在我的情况下,我没有使用 EF Core。
  • @axajkamkat 您需要将所有项目都保存在内存中。这是否是一个问题取决于用例。通常使用分页来限制返回的项目数量。
  • 很公平。一个非常有用的库是nuget.org/packages/System.Linq.Async(相当新且文档较少)。我可以使用ToListAsync() 使用该库中的扩展方法从IAsyncEnumerable&lt;T&gt; 中创建一个List&lt;T&gt;
  • 我建议不要使用 ToListAsync,因为 OP 只对异步序列的第一个元素感兴趣。等待所有元素可能会浪费时间和资源。
【解决方案2】:

如果单个返回的UserEntity 有可能是null,并且您想区分无实体和单空实体,您可以安装System.Linq.Async 包并执行以下操作:

public async Task<UserModel> GetUserByUserIdAsync(string userId)
{
    IQueryable<UserEntity> usersQuery = BuildQueryable(userId);

    var (userEntity, exists) = await usersQuery
        .AsAsyncEnumerable()
        .Select(x => (x, true))
        .FirstOrDefaultAsync();

    if (!exists) return null; // wanna do other logic in the future
    return userEntity.ToUserModel();
}

这个查询利用了ValueTuple&lt;UserEntity, bool&gt; 的默认值是(null, false)这一事实。

使用AsAsyncEnumerable 可能不如使用SingleOrDefaultAsync 方法高效,因为数据提供者可能会创建一个不太优化的执行计划。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-05-23
    • 1970-01-01
    • 2016-07-05
    • 1970-01-01
    • 2011-10-28
    • 2013-07-01
    相关资源
    最近更新 更多