请看下面的两个方法。第一个返回一个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
(在object videoID = firstRecord["VideoID"];
)时,我得到了这个异常:
读取器关闭时调用元数据的尝试无效。
这是因为SqlDataReader
被释放了。有人能提供一个推荐的方法来以异步的方式枚举SqlDataReader
,以便每个结果记录都可用于调用方法吗?谢谢。
6条答案
按热度按时间50few1ms1#
在这种情况下,LINQ不是您的朋友,因为
FirstAsync
将在 * 返回结果之前关闭迭代器,这不是ADO .NET所期望的;基本上:不要在这里使用LINQ,或者至少:你可以使用Select
之类的工具来执行投影 *,而序列仍然是打开的 *,或者将所有的工作卸载给Dapper之类的工具会更容易,或者手动执行:qf9go6mv2#
你可以通过不返回依赖于连接仍然打开的对象来避免这种情况。例如,如果你只需要
VideoID
,那么只返回它(我假设它是int
):或者投影到你自己的类中:
或者按照Marc的建议,在第一个之后使用
foreach
和break
,在您的情况下看起来像这样:zy1mlcev3#
当你公开一个打开的
DataReader
时,关闭它沿着底层Connection
的责任现在属于调用者,所以你不应该释放任何东西。相反,你应该使用DbCommand.ExecuteReaderAsync
重载,它接受CommandBehavior
参数,并传递CommandBehavior.CloseConnection
值:执行该命令时,关联的Connection对象将在关联的DataReader对象关闭时关闭。
然后你就可以希望调用者会遵守规则,及时调用
DataReader.Close
方法,并且在对象被垃圾收集之前不会让连接打开。因此,公开一个打开的DataReader
应该被认为是一种极端的性能优化技术,应该谨慎使用。顺便说一句,如果你返回一个
IEnumerable<IDataRecord>
而不是一个IAsyncEnumerable<IDataRecord>
,你也会遇到同样的问题。vzgqcmou4#
要添加到其他答案中,您可以使您的实用程序方法成为泛型,并添加投影委托
Func<IDataRecord, T> projection
作为参数,如下所示:然后在调用时传入一个lambda或引用一个方法组,例如:
例如:
5gfr0r5j5#
在2021年年底的时候,我有一个确切的问题。我找不到一个完整的例子,所以我只是在我能找到的东西周围乱转,直到我找到一些工作。
下面是我的代码完整,虽然很简单(所以你可以稍后扩展它)的例子,沿着一些注解,详细说明了我在这个过程中遇到的一些问题:
然后,在(异步)代码的任何其他部分,都可以在
await foreach
循环中调用函数:我还发现了几件事:你不能在
try
中使用yield
,但是你可以把cmd.ExecuteReaderAsync()
之前的所有东西都塞进try
,或者一个单独的方法来返回DataReader。或者你可以把await foreach
Package 在try
块中;我认为问题在于屈服于尝试之外的调用者(这是有道理的,在你考虑之后)。如果你使用另一个方法来生成读取器,* 将连接传递给那个方法 *,这样你就可以控制它的生存期。如果你的方法创建了连接,执行了命令,并返回了
SqlDataReader
,那么在你从读取器读取之前,连接就会关闭(如果你使用了'using')。同样,如果你仔细想想,这是完全有道理的,但是它让我困惑了几分钟。祝你好运,我希望这对将来的别人有帮助!
ndasle7k6#
我推荐这样的东西: