简体   繁体   English

将 IAsyncEnumerable 与 Dapper 一起使用

[英]Using IAsyncEnumerable with Dapper

We have recently migrated our ASP.NET Core API which uses Dapper to .NET Core 3.1.我们最近将使用Dapper ASP.NET Core API 迁移到 .NET Core 3.1。 After the migration, we felt there was an opportunity to use the latest IAsyncEnumerable feature from C# 8 for one of our endpoints.迁移后,我们觉得有机IAsyncEnumerable C# 8最新的IAsyncEnumerable功能用于我们的端点之一。

Here is the pseudocode before the changes:这是更改前的伪代码:

public async Task<IEnumerable<Item>> GetItems(int id)
{
    var reader = await _connection.QueryMultipleAsync(getItemsSql,
       param: new
       {
           Id = id
       });

    var idFromDb = (await reader.ReadAsync<int?>().ConfigureAwait(false)).SingleOrDefault();
    if (idFromDb == null)
    {
       return null;
    }

    var items = await reader.ReadAsync<Item>(buffered: false).ConfigureAwait(false);

    return Stream(reader, items);
} 

private IEnumerable<Item> Stream(SqlMapper.GridReader reader, IEnumerable<Item> items)
{
    using (reader)
    {
        foreach (var item in items)
        {
            yield return item;
        }
    }     
}

After IAsyncEnumerable code changes: IAsyncEnumerable代码更改后:

// Import Nuget pacakage: System.Linq.Async

public async Task<IAsyncEnumerable<Item>> GetItems(int id)
{
    var reader = await _connection.QueryMultipleAsync(getItemsSql,
       param: new
       {
           Id = id
       });

    var idFromDb = (await reader.ReadAsync<int?>().ConfigureAwait(false)).SingleOrDefault();
    if (idFromDb == null)
    {
        return null;
    }

    var items = await reader.ReadAsync<Item>(buffered: false).ConfigureAwait(false);

    return Stream(reader, items);
} 

private IAsyncEnumerable<Item> Stream(SqlMapper.GridReader reader, IEnumerable<Item> items)
{
    using (reader)
    {
       await foreach (var item in items.ToAsyncEnumerable())
       {
           yield return item;
       }
    }
 }

The above approach is to use ToAsyncEnumerable is loosely inspired from this post , but I'm not 100% sure if I'm using it in the right place/ context.上面的方法是使用ToAsyncEnumerable的灵感来自这篇文章,但我不能 100% 确定我是否在正确的位置/上下文中使用它。

Question:题:

  • The dapper library only returns IEnumerable but can we use ToAsyncEnumerable to convert it into IAsyncEnumerable for async stream like above? dapper 库只返回IEnumerable但我们可以使用ToAsyncEnumerable将其转换为IAsyncEnumerable以用于上述async stream吗?

Note : This question looks similar to What happens with returning IEnumerable if used with async/await (streaming data from SQL Server with Dapper)?注意:这个问题看起来类似于如果与 async/await 一起使用,返回 IEnumerable 会发生什么(使用 Dapper 从 SQL Server 流式传输数据)? but I do not think that answers my question.但我认为这不能回答我的问题。

Update: I wasn't aware of async iterators when I first wrote this answer.更新:当我第一次写这个答案时,我不知道异步迭代器。 Thanks to Theodor Zoulias for pointing it out.感谢 Theodor Zoulias 指出这一点。 In light of that, a much simpler approach is possible:有鉴于此,可以采用更简单的方法:

using var reader = await connection.ExecuteReaderAsync(query, parameters);
var rowParser = reader.GetRowParser<T>();

while (await reader.ReadAsync()) {
    yield return rowParser(reader);
}

Original Answer:原答案:

Here's an IAsyncEnumerable wrapper I wrote that may help those who want to stream unbuffered data using async/await and also want the power of Dapper's type mapping:这是我编写的IAsyncEnumerable包装器,它可以帮助那些想要使用 async/await 流式传输无缓冲数据并且还想要 Dapper 类型映射的强大功能的人:

public class ReaderParser<T> : IAsyncEnumerable<T> {
    public ReaderParser(SqlDataReader reader) {
        Reader = reader;
    }
    private SqlDataReader Reader { get; }
    public IAsyncEnumerator<T> GetAsyncEnumerator(CancellationToken cancellationToken = default) {
        return new ReaderParserEnumerator<T>(Reader);
    }
}
public class ReaderParserEnumerator<T> : IAsyncEnumerator<T> {
    public ReaderParserEnumerator(SqlDataReader reader) {
        Reader = reader;
        RowParser = reader.GetRowParser<T>();
    }
    public T Current => Reader.FieldCount == 0 ? default(T) : RowParser(Reader);
    private SqlDataReader Reader { get; }
    private Func<IDataReader, T> RowParser { get; }
    public async ValueTask DisposeAsync() {
        await Reader.DisposeAsync();
    }
    public async ValueTask<bool> MoveNextAsync() {
        return await Reader.ReadAsync();
    }
}

Usage:用法:

var reader = await command.ExecuteReaderAsync();
return new ReaderParser<T>(reader);

And then, package System.Linq.Async adds basically all the nice IEnumerable extensions you know and love, eg in my usage:然后,包System.Linq.Async基本上添加了所有你知道和喜欢的不错的IEnumerable扩展,例如在我的用法中:

var streamData = await repo.GetDataStream();
var buffer = await streamData.Take(BATCH_SIZE).ToListAsync();

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM