Каково использование/преимущество использования 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