Czy konieczne jest ręczne zamknięcie i usunięcie SqlDataReader?


90

Pracuję tutaj ze starszym kodem i jest wiele jego przypadków SqlDataReader, które nigdy nie są zamykane ani usuwane. Połączenie jest zamknięte, ale nie jestem pewien, czy konieczne jest ręczne zarządzanie czytnikiem.

Czy może to spowodować spowolnienie wydajności?

Odpowiedzi:


125

Staraj się unikać takich czytników:

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

Zamiast tego opakuj je w instrukcje:

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

Oświadczenie o korzystaniu zapewni prawidłowe usunięcie przedmiotu i uwolnienie zasobów.

Jeśli zapomnisz, zostawisz sprzątanie śmietnikowi, co może chwilę potrwać.


24
Nie potrzebujesz instrukcji .Close () w żadnej z próbek: jest obsługiwana przez wywołanie .Dispose ().
Joel Coehoorn

7
Prawdopodobnie chcę sprawdzić, czy to .HasRows, a nie null.
JonH

3
@Andrew Jeśli ExecuteReader zgłasza wyjątek, w jaki sposób może zwrócić wartość null?
csauve

7
@JohH: while (reader.Read ()) w tym przykładzie działa tak samo jak .HasRows i musisz .Read mimo wszystko, aby przejść do pierwszego wiersza.
csauve

1
@csauve Masz rację, przypuszczam, że nie zwróciło to wartości null. Nie jestem pewien, dlaczego patrzyłem na wartość zmiennej SqlDataReader.
Andrew,

54

Należy zauważyć, że usunięcie SqlDataReader utworzonego za pomocą SqlCommand.ExecuteReader () nie spowoduje zamknięcia / usunięcia podstawowego połączenia.

Istnieją dwa wspólne wzorce. W pierwszym czytnik jest otwierany i zamykany w ramach połączenia:

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

Czasami wygodnie jest mieć metodę dostępu do danych, otwierającą połączenie i zwracającą czytnik. W takim przypadku ważne jest, aby zwrócony czytnik został otwarty za pomocą CommandBehavior.CloseConnection, aby zamknięcie / usunięcie czytnika zamknęło podstawowe połączenie. Wzór wygląda mniej więcej tak:

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;
    }
}

a kod wywołujący musi po prostu pozbyć się czytnika w ten sposób:

using(SqlDataReader reader = ExecuteReader(...))
{
    ... do your stuff ...
} // reader and connection are closed here.

W drugim fragmencie kodu, w którym metoda zwraca SqlDataReader, polecenie nie jest usuwane. Czy to w porządku i czy można usunąć polecenie (umieścić je w bloku using), a następnie zwrócić czytnik?
zawsze ucząc się

@alwayslearning to jest dokładnie scenariusz, który mam ...... czy możesz zamknąć / pozbyć się SqlCommand, gdy zwracasz SqlDataReader do dzwoniącego?
ganders

1
To jest złe. Jeśli NAPRAWDĘ nie możesz znieść używania usings, finally {}po złapaniu wywołaj dispose w bloku. Zgodnie z tym zapisem udane polecenia nigdy nie zostałyby zamknięte ani usunięte.
smdrager

3
@smdrager, jeśli bliżej czytasz odpowiedź, mówi on o metodzie, która zwraca czytelnikowi. Jeśli używasz .ExecuteReader (CommandBehavior.CloseConnection); następnie pozbycie się CZYTNIKA połączenie zostanie zamknięte. Tak więc metoda wywołująca musi tylko otoczyć otrzymany czytnik instrukcją using. using (var rdr = SqlHelper.GetReader ()) {// ...} jeśli zamknąłeś go w ostatnim bloku, czytnik nie przeczyta, ponieważ połączenie jest zamknięte.
Sinaesthetic

@ganders - wracając do tego starego postu: tak, możesz i prawdopodobnie powinieneś pozbyć się SqlCommand - zaktualizowałem przykład, aby to zrobić.
Joe

11

Aby być bezpiecznym, zawiń każdy obiekt SqlDataReader instrukcją using .


Słusznie. Jednak czy faktycznie ma to wpływ na wydajność, jeśli nie ma instrukcji using?
Jon Ownbey

Instrukcja using jest tym samym, co zawijanie kodu DataReader w blok try..finally ..., z metodą close / dispose w sekcji last. Po prostu „gwarantuje”, że przedmiot zostanie usunięty w odpowiedni sposób.
Todd

1
Wynika to bezpośrednio z podanego przeze mnie łącza: „Instrukcja using zapewnia wywołanie metody Dispose, nawet jeśli wystąpi wyjątek podczas wywoływania metod na obiekcie”.
Kon

6
Continued ... „Ten sam rezultat można osiągnąć, umieszczając obiekt w bloku try, a następnie wywołując metodę Dispose w bloku final; w rzeczywistości jest to sposób tłumaczenia instrukcji using przez kompilator”.
Kon

5

Po prostu zawiń swój SQLDataReader instrukcją „using”. To powinno rozwiązać większość twoich problemów.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.