Can I keep a SqlDataReader “alive” after closing connection?

谁说胖子不能爱 提交于 2020-02-21 13:53:32

问题


Is there any way to access a SqlDataReader after the connection is closed?

Or is there any objects equivalent to SqlDataReader that I can store the reader into them and process on the objects later?

I'm receiving a pivot dataset from the server, so I can't use normal classes to process this kind of data, my model looks like this :

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; 
}

回答1:


You can't use the DataReader after the connection is closed, as it needs to use the connection to retrieve the data from the data source.

You need to read the data into a DataSet, or DataTable using the Load method, then you can close the connection.




回答2:


You can store the data from an SqlDataAdapter into a DataSet for future use:

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



回答3:


Is there any way to access a SqlDataReader after the connection is closed?

No. Once the Connection is closed, the reader is unable to read data from the connection.

However, it is possible to invert the relationship lifespan between DataReader and Connection by using the ExecuteReader or ExecuteReaderAsync calls with CommandBehavior.CloseConnection specified. In this mode, the Connection is closed when the Reader is Closed (or Disposed).

Long lived data readers with CommandBehavior.CloseConnection can be useful when you do not necessarily want to retrieve and materialize all data in the query all-at-once, with use cases such as data paging or take-while type lazy evaluation. This is Option 2, below.

Option 1 : Open Connection, Retrieve and Materialize all Data, and Close Everything

As with the other answers, in many instances for small, definite data fetches it is preferable to open Connection, Create Command, Execute Reader and fetch and materialize all data in one go like so:

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
}

Option 2 : Open Connection, Execute Command, and use a Long Lived Reader

Using CommandBehavior.CloseConnection, we can create a long-lived reader and defer closing of the Connection until the Reader is no longer needed.

In order to prevent data access objects like DataReaders bleeding into higher layer code, a yield return generator can be used to manage reader lifespans - while at the same time ensuring the reader (and thus connection) is closed once the generator is no longer required.

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://stackoverflow.com/a/744307/314291
   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.
}

Notes

  • As at C#6, async code cannot currently also use yield return, hence the need to split the helper method out of the async query (The helper could I believe be moved into a Local Function, however).
  • The code calling GenerateFoos does need to be mindful not to hold onto the Enumerable (or an Iterator of it) for longer than needed, as this will keep the underlying Reader and Connection open.


来源:https://stackoverflow.com/questions/26797839/can-i-keep-a-sqldatareader-alive-after-closing-connection

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!