Каково использование/преимущество использования CommandBehavior.CloseConnection в ExecuteReader()

Может ли кто-нибудь сказать мне, что такое CommandBehavior.CloseConnection, и каково использование/преимущество передачи этого параметра в com.ExecuteReader(CommandBehavior.CloseConnection)?

Ответы

Ответ 1

Для чтения устройства чтения данных требуется открытое соединение, и вы хотите закрыть соединения, как только сможете. Указав CommandBehavior.CloseConnection при вызове ExecuteReader, вы убедитесь, что ваш код закроет соединение, когда он закрывает устройство чтения данных.

Но вы уже должны быть распоряжаться вашими соединениями немедленно (а не только закрывать их), и в этом случае в лучшем случае это может оказаться незначительным (почти наверняка неизмеримым).

Например, этот код немедленно закрывает свое соединение (и выполняет любую другую работу, требуемую для его удаления), без указания поведения команды:

using (SqlConnection connection = new SqlConnection(connectionString))
using (SqlCommand command = new SqlCommand(commandText, connection))
{
    connection.Open();
    using (SqlDataReader reader = command.ExecuteReader()) 
    {
        while (reader.Read())
           // Do something with the rows
    }
}

Ответ 2

Используйте CommandBehavior.CloseConnection в функциях, которые создают соединение, но возвращают IDataReader. Будьте осторожны с Dispose() для IDbConnection для исключений только до создания читателя:

IDataReader ReadAll()
{
    var connection = new SqlConnection(connectionString);
    try
    {
        connection.Open();
        var command = connection.CreateCommand();
        command.CommandText = "SELECT * FROM mytable";
        var result = command.ExecuteReader(CommandBehavior.CloseConnection);
        connection = null; // Don't dispose the connection.
        return result;
    }
    finally
    {
        if (connection != null)
            connection.Dispose();
    }
}

Ответ 3

Если вы этого не сделаете, то при повторном соединении в цикле соединение останется "открытым", пока сборщик мусора не заберет его, и только после этого он будет отпущен обратно в ADO.NET Пул подключений для повторного использования. Это означает, что каждый раз через цикл код, который "открывает" соединение, не сможет повторно использовать тот же самый (он не был возвращен обратно в пул).
 В результате для каждой последующей итерации цикла ADO необходимо будет создать другое соединение с нуля, и в конечном итоге у вас могут закончиться доступные соединения. В зависимости от того, сколько времени потребуется GC для закрытия, вы могли бы пройти через большое количество итераций цикла, создавая для них новое ненужное соединение, в то время как все эти незакрытые и неиспользуемые соединения просто сидят там.  Если вы используете CommandBehavior.CloseConnection, то в каждом цикле вы отпустите соединение обратно в пул, а следующая итерация может повторно использовать его. В результате ваш процесс будет работать быстрее и может уйти со значительно меньшим количеством подключений.

Ответ 4

Я предлагаю прочитать документацию MSDN для CommandBehaviour Enumeration:

CloseConnection. Когда команда выполняется, связанный объект Connection закрывается, когда связанный объект DataReader закрывается.

Сравните это с другими элементами перечисления.

Ответ 5

Я нашел наилучшее использование CommandBehavior.CloseConnection, когда вы хотите написать достаточно гибкий код для использования в транзакции (что подразумевает совместное соединение для всех запросов). Рассмотрим:

public DbDataReader GetReader(DbCommand cmd, bool close = true)
{
    if(close)
        return cmd.ExecuteReader(CommandBehavior.CloseConnection);
    return cmd.ExecuteReader();
}

Если вы выполняете операцию чтения как часть более крупной транзакции, вам нужно передать false этому методу. В любом случае вы должны использовать оператор using для выполнения фактического чтения.

Не в транзакции:

using(var reader = GetReader(cmd, true))
{
    while(reader.Read())
        ...
}

В транзакции, возможно, проверка наличия записи:

bool exists = false;
using(var reader = GetReader(cmd, false))
{
    if(reader.Read())
        exists = reader.GetBoolean(0);
}

Первый пример закроет читатель И соединение. Второй (транзакционный) будет закрывать читателя, но не соединение.

Ответ 6

Re: В чем преимущество CommandBehavior.CloseConnection?

Долговечные считыватели данных могут быть полезны, когда вы не обязательно хотите извлекать и материализовывать все данные, которые запрос в противном случае возвращал бы, все-в-одном. Хотя ваше приложение может напрямую сохранить ссылку на долгоживущее соединение, это может привести к беспорядочной архитектуре, где зависимости уровня данных, такие как ISqlConnection, "истекают" в деловые и презентационные проблемы вашего приложения.

Ленивый поиск данных (т.е. получение данных только тогда, когда это необходимо) имеет то преимущество, что вызывающий может продолжать запрашивать больше данных, пока не удовлетворит свои требования к данным - это распространено в сценариях, требующих пейджинга данных или принятия ленивой оценки, пока не будет выполнено удовлетворительное условие.

Долгосрочное подключение/ленивые методы поиска данных, возможно, были более распространены в старых архитектурах, таких как Fat-Client, где пользователь просматривал данные, сохраняя открытое соединение, однако в современном коде все еще используются.

Здесь есть что-то вроде компромисса: хотя на время работы Reader (и Connection) и для хранения "состояния" в базе данных РСУБД есть память и накладные расходы на ресурсы для ресурсов как для приложения, так и для клиента (буферы или даже курсоры, если выполняется PROC с использованием курсоров), есть также преимущества:

  • Сеть IO между потребляющим приложением и базой данных уменьшается, так как восстанавливаются только требуемые данные
  • Накладные расходы на память приложений уменьшены, поскольку данные, которые не понадобятся, не извлекаются (и если объектные абстракции используются поверх DataReader, это также потенциально может привести к ненужной десериализации/материализации в Entity/DTO/POCO которые)
  • Не материализуя все данные в запросе "все одновременно", это позволяет приложению освобождать память (например, DTO), которая больше не нужна, когда она перемещается через считыватель.

Предотвращение кровопускания IDataReader в ваше приложение

В настоящее время большинство приложений переносят проблемы доступа к данным в шаблон репозитория или с помощью ORM для абстрактного доступа к данным - обычно это приводит к возврату данных, возвращающим объект Entity, вместо того, чтобы работать с API уровня IDataReader на протяжении всего вашего приложение.

К счастью, все еще можно использовать ленивый генератор (т.е. метод, возвращающий IEnumerable<Entity> И все еще сохраняя контроль над продолжительностью жизни DataReader (и таким образом Connection), используя такой шаблон (это версия async, но очевидно, что код синхронизации также будет работать, хотя и будет более голодным)

public Task<IEnumerable<Foo>> LazyQueryAllFoos()
{
   var sqlConn = new SqlConnection(_connectionString);
   using (var cmd = new SqlCommand(
        "SELECT Id, Col2, ... FROM LargeFoos", sqlConn))
   {
      await mySqlConn.OpenAsync();
      var reader = 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 LazyGenerateFoos(reader);
   }
}

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

Примечания

  • Как и в случае с SqlConnections и DataReaders, код, вызывающий LazyQueryAllFoos, по-прежнему должен помнить, что он не должен удерживать Enumerable (или его итератор) дольше, чем нужно, поскольку это приведет к тому, что основной Reader и Connection будут открыты.
  • Джон Скит анализирует yield return генераторы в глубине здесь - вынос заключается в том, что finally использования блоков будет завершен в итераторе yield блоки, как только перечислимый либо запустится до завершения, либо выбрал исключение (например, сетевой сбой), либо даже если вызывающий не завершил итерацию итератора, и он выходит за пределы области видимости.
  • Как и в С# 6, асинхронный код в настоящее время также не может использовать возврат доходности, следовательно, необходимо разделить вспомогательный метод из асинхронного запроса (однако хелпер может быть перенесен в локальную функцию). В будущем могут измениться, например. IAsyncEnumerator