Могу ли я сохранить SqlDataReader живым после закрытия соединения?

Есть ли способ получить доступ к 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; 
}

person NeedAnswers    schedule 07.11.2014    source источник
comment
Нет, просто используйте свой собственный класс для хранения полей.   -  person Tim Schmelter    schedule 07.11.2014
comment
Вам нужно прочитать данные в DataSet или DataTable с помощью метода Load, после чего вы можете закрыть соединение.   -  person Ben    schedule 07.11.2014
comment
@Бен: ему даже не нужен DataTable/DataSet, если он уже использует SqlDataReader. Ему просто нужно соответствующим образом инициализировать класс из полей считывателя.   -  person Tim Schmelter    schedule 07.11.2014
comment
@TimSchmelter, это сработает после того, как он закроет соединение?   -  person Ben    schedule 07.11.2014
comment
@Ben: вы не можете использовать средство чтения, если соединение закрыто, но поэтому у вас есть класс и его поля, которые не связаны с базой данных.   -  person Tim Schmelter    schedule 07.11.2014
comment
@TimSchmelter, это динамический набор результатов, я не могу сохранить его в классе   -  person NeedAnswers    schedule 07.11.2014
comment
@hoangnnm: Что это значит? Возможно, вы могли бы использовать поставщика LINQ, такого как Linq-To-Sql, и использовать его отложенное выполнение возможностей.   -  person Tim Schmelter    schedule 07.11.2014
comment
@TimSchmelter Я решил с помощью DataTable, все равно спасибо   -  person NeedAnswers    schedule 07.11.2014
comment
@hoangnnm: если вы можете загрузить DataTable, вы также можете загрузить типизированный List<OneToNinetyNine> (или что-то еще).   -  person Tim Schmelter    schedule 07.11.2014
comment
@TimSchmelter, но есть динамические столбцы! Или может я не правильно понял вашу мысль!   -  person NeedAnswers    schedule 07.11.2014
comment
@hoangnnm: у вас недостаточно информации, чтобы предоставить что-то полезное. Что динамично, что статично? Вы можете зациклить все поля DataReader, даже если вы не знаете столбцы. Таким образом, вы можете загрузить, например, свойство List<string> as в нашем классе для динамической части.   -  person Tim Schmelter    schedule 07.11.2014


Ответы (3)


Вы не можете использовать DataReader после закрытия соединения, так как ему необходимо использовать соединение для извлечения данных из источника данных.

Вам нужно прочитать данные в DataSet или DataTable с помощью метода Load, после чего вы можете закрыть соединение.

person Ben    schedule 07.11.2014
comment
Спасибо, буду придерживаться DataSet - person NeedAnswers; 07.11.2014

Вы можете сохранить данные из SqlDataAdapter в DataSet для будущего использования:

DataSet ds = new DataSet();
SqlCommand mycommand = new SqlCommand("sql statement");
using (SqlDataAdapter adapter = new SqlDataAdapter(mycommand))
{
    adapter.Fill(ds);
}
person Tea With Cookies    schedule 07.11.2014

Есть ли способ получить доступ к SqlDataReader после закрытия соединения?

Нет. После того, как соединение закрыто, считыватель не может прочитать данные из соединения.

Однако можно инвертировать продолжительность жизни отношения между DataReader и Connection, используя вызовы ExecuteReader или ExecuteReaderAsync с указанным CommandBehavior.CloseConnection. В этом режиме соединение закрывается, когда считыватель закрыт (или ликвидирован).

Долгоживущие считыватели данных с CommandBehavior.CloseConnection могут быть полезны, когда вы не обязательно хотите извлекать и материализовывать все данные в запросе сразу, с такими вариантами использования, как подкачка данных или отложенное вычисление типа take-while. Это Вариант 2, ниже.

Вариант 1. Открыть соединение, получить и материализовать все данные и закрыть все

Как и в случае с другими ответами, во многих случаях для выборки небольших определенных данных предпочтительнее открывать Connection, Create Command, Execute 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, мы можем создать долгоживущий ридер и отложить закрытие Connection до тех пор, пока 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://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.
}

Примечания

  • Как и в C # 6, код async в настоящее время также не может использовать yield return, поэтому необходимо отделить вспомогательный метод от асинхронного запроса (однако, вспомогательный метод, я думаю, можно переместить в локальную функцию).
  • Код, вызывающий GenerateFoos, должен помнить о том, что не следует удерживать Enumerable (или его итератор) дольше, чем необходимо, так как это будет держать открытыми лежащие в основе Reader и Connection.
person StuartLC    schedule 19.11.2017
comment
Nb не ограничивайте использование вокруг Conn или читателя. Не нужна строка $ - person StuartLC; 19.11.2017