我可以在关闭连接后保持SqlDataReader的“活性”吗?

5

在连接关闭后,是否有任何方式可以访问SqlDataReader

或者是否有等效于SqlDataReader的对象,我可以将读取器存储到其中,并稍后在这些对象上进行处理?

我从服务器接收一个数据透视表数据集,因此无法使用普通类来处理此类数据,我的模型如下:

public class OneToNinetyNine
{
    public List<Cities> listCities;
    public string CityID;
    public DateTime DateFrom;
    public DateTime DateTo;
    // this is the reader that I attempt to pass to the views 
    public SqlDataReader SqlReader; 
}

不,只需使用您自己的类来存储字段即可。 - Tim Schmelter
你需要使用Load方法将数据读入DataSetDataTable中,然后可以关闭连接。 - Ben
@Ben:如果他已经使用了SqlDataReader,那么他甚至不需要一个DataTable/DataSet。他只需要从读取器的字段适当地初始化类即可。 - Tim Schmelter
@TimSchmelter,在他关闭连接后,这还能正常工作吗? - Ben
1
@hoangnnm:如果你可以加载一个DataTable,那么你也可以加载一个类型化的List<OneToNinetyNine>(或其他)。 - Tim Schmelter
显示剩余6条评论
3个回答

5

当连接关闭后,您不能使用DataReader,因为它需要使用连接从数据源检索数据。

您需要使用Load方法将数据读入DataSetDataTable中,然后可以关闭连接。


谢谢,我会坚持使用 DataSet。 - NeedAnswers

4

您可以将 SqlDataAdapter 中的数据存储到 DataSet 中,以备将来使用:

DataSet ds = new DataSet();
SqlCommand mycommand = new SqlCommand("sql statement");
using (SqlDataAdapter adapter = new SqlDataAdapter(mycommand))
{
    adapter.Fill(ds);
}

2
在连接关闭后,无法访问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; // We're done with our Sql data access here
      } // Reader Disposed here
   } // Command and Connection Disposed here
}

选项2:打开连接,执行命令并使用长寿命读取器

使用 CommandBehavior.CloseConnection,我们可以创建一个长寿命的读取器,并延迟关闭连接,直到不再需要Reader

为了防止像 DataReaders 这样的数据访问对象泄露到更高层代码中,可以使用 yield return 生成器来管理读取器的生命周期 - 同时确保一旦不再需要生成器即关闭读取器(因此也关闭连接)。

public async Task<IEnumerable<Foo>> LazyQueryAllFoos()
{
   // NB : No `using` ... the reader controls the lifetime of the connection.
   var sqlConn = new SqlConnection(_connectionString);
   // Yes, it is OK to dispose of the Command https://dev59.com/t3RB5IYBdhLWcg3wCjYV#744307
   using (var cmd = new SqlCommand(
        $"SELECT Col1, Col2, ... FROM LargeFoos", mySqlConn))
   {
      await mySqlConn.OpenAsync();
      var reader = await cmd.ExecuteReaderAsync(CommandBehavior.CloseConnection);
      // Return the IEnumerable, without actually materializing Foos yet.
      // Reader and Connection remain open until caller is done with the enumerable
      return GenerateFoos(reader);
   }
}

// Helper method to manage lifespan of foos
private static IEnumerable<Foo> GenerateFoos(IDataReader reader)
{
    using(reader)
    {
       while (reader.Read())
       {
          yield return new Foo
          {
              Id = Convert.ToInt32(reader["Id"]),
              ...
          };
       }
    } // Reader is Closed + Disposed here => Connection also Closed.
}

注意事项

  • 截至C#6版本,async代码目前无法同时使用yield return,因此需要将辅助方法从异步查询中拆分出来(但我相信这个辅助方法可以移动到本地函数中)。
  • 调用GenerateFoos的代码需要注意不要长时间持有Enumerable(或其迭代器),否则将保持底层的Reader和Connection开启。

不要在Conn或reader周围使用using范围。不需要$字符串。 - StuartLC

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接