在连接关闭后,无法访问SqlDataReader。然而,可以通过使用ExecuteReader或ExecuteReaderAsync调用并指定CommandBehavior.CloseConnection来翻转DataReader和Connection之间的关系寿命。在此模式下,当Reader被关闭(或Disposed)时,Connection也会关闭。具有CommandBehavior.CloseConnection的长生命周期数据读取器可在不一定想要一次检索和实现查询中的所有数据的情况下使用,例如数据分页或take-while类型的惰性评估。这是下面的选项2。选项1:打开连接,检索和实现所有数据,然后关闭所有内容。对于小型、明确的数据获取,在许多情况下,像其他答案一样,最好打开连接、创建命令、执行Reader并一次性获取和实现所有数据。
public async Task<Foo> GetOneFoo(int idToFetch)
{
using (var myConn = new SqlConnection(_connectionString))
using (var cmd = new SqlCommand("SELECT Id, Col2, ... FROM Foo WHERE Id = @Id"))
{
await myConn.OpenAsync();
cmd.Parameters.AddWithValue("@Id", idToFetch);
using (var reader = await cmd.ExecuteReaderAsync())
{
var myFoo = new Foo
{
Id = Convert.ToInt32(reader["Id"]),
... etc
}
return myFoo;
}
}
}
选项2:打开连接,执行命令并使用长寿命读取器
使用 CommandBehavior.CloseConnection
,我们可以创建一个长寿命的读取器,并延迟关闭连接,直到不再需要Reader
。
为了防止像 DataReaders
这样的数据访问对象泄露到更高层代码中,可以使用 yield return
生成器来管理读取器的生命周期 - 同时确保一旦不再需要生成器即关闭读取器(因此也关闭连接)。
public async Task<IEnumerable<Foo>> LazyQueryAllFoos()
{
var sqlConn = new SqlConnection(_connectionString);
using (var cmd = new SqlCommand(
$"SELECT Col1, Col2, ... FROM LargeFoos", mySqlConn))
{
await mySqlConn.OpenAsync();
var reader = await cmd.ExecuteReaderAsync(CommandBehavior.CloseConnection);
return GenerateFoos(reader);
}
}
private static IEnumerable<Foo> GenerateFoos(IDataReader reader)
{
using(reader)
{
while (reader.Read())
{
yield return new Foo
{
Id = Convert.ToInt32(reader["Id"]),
...
};
}
}
}
注意事项
- 截至C#6版本,
async
代码目前无法同时使用yield return,因此需要将辅助方法从异步查询中拆分出来(但我相信这个辅助方法可以移动到本地函数中)。
- 调用
GenerateFoos
的代码需要注意不要长时间持有Enumerable(或其迭代器),否则将保持底层的Reader和Connection开启。
Load
方法将数据读入DataSet
或DataTable
中,然后可以关闭连接。 - BenSqlDataReader
,那么他甚至不需要一个DataTable
/DataSet
。他只需要从读取器的字段适当地初始化类即可。 - Tim SchmelterDataTable
,那么你也可以加载一个类型化的List<OneToNinetyNine>
(或其他)。 - Tim Schmelter