首页 > 解决方案 > 如何使用 SqlDataReader 返回和使用 IAsyncEnumerable

问题描述

请看以下两种方法。第一个返回一个IAsyncEnumerable. 第二个试图消耗它。

using System.Collections.Generic;
using System.Data;
using System.Data.SqlClient;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;

public static class SqlUtility
{
    public static async IAsyncEnumerable<IDataRecord> GetRecordsAsync(
        string connectionString, SqlParameter[] parameters, string commandText,
        [EnumeratorCancellation]CancellationToken cancellationToken)
    {
        using (SqlConnection connection = new SqlConnection(connectionString))
        {
            await connection.OpenAsync(cancellationToken).ConfigureAwait(false);
            using (SqlCommand command = new SqlCommand(commandText, connection))
            {
                command.Parameters.AddRange(parameters);
                using (var reader = await command.ExecuteReaderAsync()
                    .ConfigureAwait(false))
                {
                    while (await reader.ReadAsync().ConfigureAwait(false))
                    {
                        yield return reader;
                    }
                }
            }
        }
    }

    public static async Task Example()
    {
        const string connectionString =
            "Server=localhost;Database=[Redacted];Integrated Security=true";
        SqlParameter[] parameters = new SqlParameter[]
        {
            new SqlParameter("VideoID", SqlDbType.Int) { Value = 1000 }
        };
        const string commandText = "select * from Video where VideoID=@VideoID";
        IAsyncEnumerable<IDataRecord> records = GetRecordsAsync(connectionString,
            parameters, commandText, CancellationToken.None);
        IDataRecord firstRecord = await records.FirstAsync().ConfigureAwait(false);
        object videoID = firstRecord["VideoID"]; //Should be 1000.
        // Instead, I get this exception:
        // "Invalid attempt to call MetaData when reader is closed."
    }
}

当代码尝试读取结果IDataReader(at object videoID = firstRecord["VideoID"];)时,我得到了这个异常:

阅读器关闭时调用元数据的尝试无效。

这是因为SqlDataReader被处置。有人可以提供推荐的方法来SqlDataReader以异步方式枚举,以便调用方法可以使用每个结果记录吗?谢谢你。

标签: c#.netasync-awaitsqldatareaderiasyncenumerable

解决方案


在这种情况下,LINQ 不是您的朋友,因为FirstAsync它将在迭代器返回结果之前关闭它,这不是 ADO.NET 所期望的;基本上:不要在这里使用 LINQ,或者至少:不要以这种方式。您可能可以使用类似的东西在序列仍然打开时Select执行投影,或者将这里的所有工作卸载到像 Dapper 这样的工具可能更容易。或者,手动进行:

await foreach (var record in records)
{
    // TODO: process record
    // (perhaps "break"), because you only want the first
}

推荐阅读