Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/.net/23.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C# 如何使用SqlDataReader返回和使用IAsyncEnumerable_C#_.net_Async Await_Sqldatareader_Iasyncenumerable - Fatal编程技术网

C# 如何使用SqlDataReader返回和使用IAsyncEnumerable

C# 如何使用SqlDataReader返回和使用IAsyncEnumerable,c#,.net,async-await,sqldatareader,iasyncenumerable,C#,.net,Async Await,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 stati

请参见以下两种方法。第一个返回一个
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."
    }
}
使用System.Collections.Generic;
使用系统数据;
使用System.Data.SqlClient;
使用System.Linq;
使用System.Runtime.CompilerServices;
使用系统线程;
使用System.Threading.Tasks;
公共静态类SqlUtility
{
公共静态异步IAsyncEnumerable GetRecordsAsync(
string connectionString,SqlParameter[]参数,string commandText,
[EnumeratorCancellation]CancellationToken CancellationToken)
{
使用(SqlConnection连接=新的SqlConnection(connectionString))
{
wait connection.OpenAsync(cancellationToken).configurewait(false);
使用(SqlCommand=newsqlcommand(commandText,connection))
{
command.Parameters.AddRange(参数);
使用(var reader=await command.ExecuteReaderAsync()
.ConfigureWait(错误))
{
while(wait reader.ReadAsync().configurewait(false))
{
回传阅读器;
}
}
}
}
}
公共静态异步任务示例()
{
常量字符串连接字符串=
“服务器=本地主机;数据库=[修订];集成安全性=真”;
SqlParameter[]参数=新的SqlParameter[]
{
新的SqlParameter(“VideoID”,SqlDbType.Int){Value=1000}
};
const string commandText=“从视频中选择*,其中VideoID=@VideoID”;
IAsyncEnumerable records=GetRecordsAsync(connectionString,
参数、commandText、CancellationToken.None);
IDataRecord firstRecord=await records.FirstAsync().ConfigureAwait(false);
object videoID=firstRecord[“videoID”];//应该是1000。
//相反,我得到了一个例外:
//“在读取器关闭时调用元数据的尝试无效。”
}
}
当代码尝试读取结果
IDataReader
(在
object videoID=firstRecord[“videoID”];
)时,我得到以下异常:

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


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

在此场景中,LINQ不是您的朋友,因为
FirstAsync
将在迭代器返回结果之前关闭迭代器,这不是ADO.NET所期望的;基本上:不要在这里使用LINQ,或者至少:不要以这种方式使用。您可以使用
Select
之类的工具在序列仍处于打开状态时执行投影,或者可以更轻松地将此处的所有工作卸载到Dapper之类的工具。或者,要手动执行此操作:

wait foreach(记录中的var记录)
{
//TODO:进程记录
//(也许是“打破”),因为你只想要第一个
}

您可以通过不返回依赖于仍处于打开状态的连接的对象来避免这种情况。例如,如果您只需要
VideoID
,那么只需返回它(我假设它是
int
):


当您公开一个打开的
数据读取器
时,关闭它以及基础
连接
的责任现在属于调用者,因此您不应该处理任何东西。相反,您应该使用接受参数的重载,并传递
CommandBehavior.CloseConnection
值:

执行该命令时,关联的DataReader对象关闭时,关联的连接对象关闭

然后您可以希望调用者按照规则进行操作,并立即调用
DataReader.Close
方法,并且在对象被垃圾收集之前不会让连接打开。因此,公开一个开放的
DataReader
应该被认为是一种极端的性能优化技术,应该少用


顺便说一句,如果您返回一个
IEnumerable
而不是一个
IAsyncenuerable

来添加到其他答案中,您也会遇到同样的问题,您可以使您的实用工具方法通用,并添加一个投影委托,
Func projection
,作为如下参数:

public static async IAsyncEnumerable<T> GetRecordsAsync<T>(
    string connectionString, SqlParameter[] parameters, string commandText,
    Func<IDataRecord, T> projection, // Parameter here
    [EnumeratorCancellation] CancellationToken cancellationToken)
{
    ...
                    yield return projection(reader); // Projected here
    ...
}
例如:


我现在明白了。非常感谢。你的回答以最简洁的方式补充了我所缺少的知识。我使用了你告诉我的方法,在处理阅读器之前将记录数据缓存到字典中。虽然我没有这样做,但我认为这是一个有趣的解决方案。你知道命令什么时候被释放吗?当读卡器和连接保持打开状态时,它是否会被正常处理?如果不关闭或处理连接,当垃圾收集器回收该对象时,它将被关闭。由于GC过程是不确定的,因此确切的时间尚未定义。谢谢。我明白。我在问命令的事。当读卡器关闭时,连接将关闭。我想知道这个命令。再次感谢。所有一次性内置物品都是一样的。如果您没有明确地处理它们,它们将在回收时被处理。好的。我想知道在释放读卡器时,该命令是否会与连接一起被释放。也许我可以使用
GetRecordsAsync
中的
语句正常地处理该命令。我不确定这是否会使读卡器绊倒,因为它将在命令释放后使用。也许有一天我会尝试一下。谢谢,好主意。但这并不能阻止某人将
IDataRecord
投影到自身(
GetRecordsAsync(…,x=>x,…
),从而导致相同的问题
public class MyRecord {
    public int VideoId { get; set; }
}

public static async IAsyncEnumerable<MyRecord> GetRecordsAsync(string connectionString, SqlParameter[] parameters, string commandText, [EnumeratorCancellation]CancellationToken cancellationToken)
{
    ...
                    yield return new MyRecord {
                        VideoId = reader["VideoID"]
                    }
    ...
}
IAsyncEnumerable<IDataRecord> records = GetRecordsAsync(connectionString, parameters, commandText, CancellationToken.None);
object videoID;
await foreach (var record in records)
{
    videoID = record["VideoID"];
    break;
}
public static async IAsyncEnumerable<T> GetRecordsAsync<T>(
    string connectionString, SqlParameter[] parameters, string commandText,
    Func<IDataRecord, T> projection, // Parameter here
    [EnumeratorCancellation] CancellationToken cancellationToken)
{
    ...
                    yield return projection(reader); // Projected here
    ...
}
public static object GetVideoId(IDataRecord dataRecord)
    => dataRecord["VideoID"];
GetRecordsAsync(connectionString, parameters, commandText, GetVideoId, CancellationToken.None);