Чи потрібно вручну закривати та утилізувати 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(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

Заява про використання забезпечить правильне розпорядження об’єктом та звільнення ресурсів.

Якщо ви забудете, тоді ви залишаєте прибирання сміттєзбірнику, що може зайняти деякий час.


24
Вам не потрібен оператор .Close () ні в одному зразку: він обробляється викликом .Dispose ().
Джоел Коухорн,

7
Можливо, хочеться перевірити, чи він має .HasRows, а не нуль.
JonH

3
@Andrew Якщо ExecuteReader видає виняток, як він може повернути null?
csauve

7
@JohH: у прикладі while (reader.Read ()) виконує те саме, що і .HasRows, і вам потрібно. Прочитати будь-коли, щоб перенести читач вперед у перший рядок.
csauve

1
@csauve Ви маєте рацію, мабуть, воно, мабуть, не повернуло null. Я не впевнений, чому я дивився на значення змінної SqlDataReader.
Ендрю

53

Зверніть увагу, що утилізація 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, команда не використовується. Це нормально і чи нормально розпоряджатися командою (вкласти її у використовуючий блок), а потім повертати зчитувач?
завждинавчання

@alwayslearning це саме той сценарій, який я маю ...... чи можете ви закрити / утилізувати SqlCommand, коли повертаєте SqlDataReader абоненту?
ganders

1
Це погано. Якщо ви дійсно не можете терпіти використання usings, тоді зателефонуйте dispose у finally {}блоці після catch. Так, як це пишеться, успішні команди ніколи не закриваються та не видаляються.
smdrager

2
@smdrager, якщо прочитати відповідь ближче, він говорить про метод, який повертає читача. Якщо ви використовуєте .ExecuteReader (CommandBehavior.CloseConnection); тоді, утилізувавши ЧИТАТЕЛЬ, з'єднання буде закрито. Тож методу виклику потрібно лише обернути отриманий зчитувач оператором using. використовуючи (var rdr = SqlHelper.GetReader ()) {// ...} якщо б ви закрили його в блоці «нарешті», то ваш читач не зміг би прочитати, оскільки з'єднання закрито.
Синестетичний

@ganders - повертаючись до цього старого допису: так, ти можеш і, мабуть, повинен розпоряджатися SqlCommand - оновив приклад, щоб зробити це.
Джо

11

Щоб бути в безпеці, оберніть кожен об’єкт SqlDataReader у оператор using .


Справедливо. Однак чи насправді це впливає на продуктивність, якщо немає оператора використання?
Джон Оунбі

Інструкція using - це те саме, що обгортання коду DataReader у блок try..finally ..., з методом close / dispose у розділі нарешті. В основному це просто "гарантує", що об'єкт буде утилізований належним чином.
Тодд,

Це прямо з посилання, яке я надав: "Інструкція using забезпечує виклик Dispose, навіть якщо виникає виняток під час виклику методів на об'єкті".
Kon

5
Продовження ... "Ви можете досягти того самого результату, помістивши об'єкт всередину блоку try, а потім викликавши Dispose у блоці нарешті; насправді, так перекладається компілятором оператор using."
Kon

5

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

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.