Нужно ли вручную закрывать и удалять SqlDataReader?

90

Здесь я работаю с устаревшим кодом, и многие его экземпляры SqlDataReaderникогда не закрываются и не удаляются. Соединение закрыто, но я не уверен, нужно ли управлять считывателем вручную.

Может ли это вызвать снижение производительности?

Джон Оунбей
источник

Ответы:

125

Старайтесь избегать использования таких читателей:

SqlConnection connection = new SqlConnection("connection string");
SqlCommand cmd = new SqlCommand("SELECT * FROM SomeTable", connection);
SqlDataReader reader = cmd.ExecuteReader();
connection.Open();
if (reader != null)
{
      while (reader.Read())
      {
              //do something
      }
}
reader.Close(); // <- too easy to forget
reader.Dispose(); // <- too easy to forget
connection.Close(); // <- too easy to forget

Вместо этого оберните их операторами using:

using(SqlConnection connection = new SqlConnection("connection string"))
{

    connection.Open();

    using(SqlCommand cmd = new SqlCommand("SELECT * FROM SomeTable", connection))
    {
        using (SqlDataReader reader = cmd.ExecuteReader())
        {
            if (reader != null)
            {
                while (reader.Read())
                {
                    //do something
                }
            }
        } // reader closed and disposed up here

    } // command disposed here

} //connection closed and disposed here

Оператор using обеспечит правильное удаление объекта и освобождение ресурсов.

Если вы забудете, вы оставите уборку сборщику мусора, что может занять некоторое время.

Codebrain
источник
24
Вам не нужен оператор .Close () ни в одном из примеров: он обрабатывается вызовом .Dispose ().
Джоэл Коэхорн,
7
Вероятно, хотите проверить, если это .HasRows, а не null.
JonH
3
@Andrew Если ExecuteReader выдает исключение, как он может вернуть null?
csauve
7
@JohH: while (reader.Read ()) в примере выполняет то же самое, что и .HasRows, и вам нужно .Read в любом случае, чтобы переместить читателя вперед к первой строке.
csauve
1
@csauve Вы правы, я думаю, он не вернул null. Я не уверен, почему я смотрел на значение переменной SqlDataReader.
Эндрю
54

Обратите внимание, что удаление экземпляра SqlDataReader, созданного с помощью SqlCommand.ExecuteReader (), не приведет к закрытию / удалению базового соединения.

Есть два общих шаблона. В первом случае считыватель открывается и закрывается в рамках соединения:

using(SqlConnection connection = ...)
{
    connection.Open();
    ...
    using(SqlCommand command = ...)
    {
        using(SqlDataReader reader = command.ExecuteReader())
        {
            ... do your stuff ...
        } // reader is closed/disposed here
    } // command is closed/disposed here
} // connection is closed/disposed here

Иногда удобно, чтобы метод доступа к данным открывал соединение и возвращал считыватель. В этом случае важно, чтобы возвращаемый считыватель открывался с помощью CommandBehavior.CloseConnection, чтобы закрытие / удаление считывателя закрыло базовое соединение. Выглядит узор примерно так:

public SqlDataReader ExecuteReader(string commandText)
{
    SqlConnection connection = new SqlConnection(...);
    try
    {
        connection.Open();
        using(SqlCommand command = new SqlCommand(commandText, connection))
        {
            return command.ExecuteReader(CommandBehavior.CloseConnection);
        }
    }
    catch
    {
        // Close connection before rethrowing
        connection.Close();
        throw;
    }
}

а вызывающий код просто должен расположить считыватель таким образом:

using(SqlDataReader reader = ExecuteReader(...))
{
    ... do your stuff ...
} // reader and connection are closed here.
Джо
источник
Во втором фрагменте кода, где метод возвращает SqlDataReader, команда не удаляется. Это нормально, и можно ли удалить команду (заключить ее в блок using), а затем вернуть читатель?
alwayslearning
@alwayslearning, это именно тот сценарий, который у меня есть ... Можете ли вы закрыть / избавиться от SqlCommand, когда вы возвращаете SqlDataReader вызывающей стороне?
ganders
1
Это плохо. Если вы ДЕЙСТВИТЕЛЬНО не можете использовать usings, тогда вызовите dispose в finally {}блоке после catch. Как это написано, успешные команды никогда не будут закрыты или удалены.
smdrager 02
3
@smdrager, если внимательно прочитать ответ, он говорит о методе, который возвращает читателя. Если вы используете .ExecuteReader (CommandBehavior.CloseConnection); затем, если вы удалите СЧИТЫВАТЕЛЬ, соединение будет закрыто. Таким образом, вызывающему методу нужно только обернуть получившийся читатель в оператор using. using (var rdr = SqlHelper.GetReader ()) {// ...} если вы закрыли его в блоке finally, ваш читатель не смог бы прочитать, потому что соединение закрыто.
Sinaesthetic
@ganders - возвращаясь к этому старому сообщению: да, вы можете и, вероятно, должны избавиться от SqlCommand - обновил пример для этого.
Джо
11

На всякий случай оберните каждый объект SqlDataReader в оператор using .

Кон
источник
Справедливо. Однако действительно ли это влияет на производительность, если нет оператора using?
Джон Олбей,
Оператор using аналогичен заключению кода DataReader в блок try..finally ... с методом close / dispose в разделе finally. По сути, это просто «гарантия» того, что объект будет правильно утилизирован.
Тодд,
1
Это прямо из предоставленной мной ссылки: «Оператор using гарантирует, что Dispose вызывается даже в случае возникновения исключения, когда вы вызываете методы объекта».
Кон,
6
Продолжение ... «Вы можете достичь того же результата, поместив объект в блок try и затем вызвав Dispose в блоке finally; фактически, именно так оператор using транслируется компилятором».
Кон,
5

Просто оберните ваш SQLDataReader оператором using. Это должно решить большинство ваших проблем.

JW
источник