w bloku „using” jest SqlConnection zamykane po powrocie lub wyjątku?

136

Pierwsze pytanie:
powiedz, że tak

using (SqlConnection connection = new SqlConnection(connectionString))
{
    connection.Open();

    string storedProc = "GetData";
    SqlCommand command = new SqlCommand(storedProc, connection);
    command.CommandType = CommandType.StoredProcedure;
    command.Parameters.Add(new SqlParameter("@EmployeeID", employeeID));

    return (byte[])command.ExecuteScalar();
}

Czy połączenie zostaje zamknięte? Ponieważ technicznie nigdy nie dotarliśmy do końca, }tak jak returnprzedtem.

Drugie pytanie:
tym razem mam:

try
{
    using (SqlConnection connection = new SqlConnection(connectionString))
    {
        int employeeID = findEmployeeID();

        connection.Open();
        SqlCommand command = new SqlCommand("UpdateEmployeeTable", connection);
        command.CommandType = CommandType.StoredProcedure;
        command.Parameters.Add(new SqlParameter("@EmployeeID", employeeID));
        command.CommandTimeout = 5;

        command.ExecuteNonQuery();
    }
}
catch (Exception) { /*Handle error*/ }

Teraz powiedzmy, że gdzieś w trypliku pojawia się błąd i zostaje on przechwycony. Czy połączenie nadal jest zamykane? Ponownie, pomijamy resztę kodu w instrukcji tryi przechodzimy bezpośrednio do catchinstrukcji.

Czy myślę zbyt liniowo, jak to usingdziała? ie Czy Dispose()po prostu zostaje wywołany, gdy opuszczamy usingzakres?

Marcus
źródło

Odpowiedzi:

178
  1. tak
  2. Tak.

Tak czy inaczej, gdy blok using zostanie zakończony (przez pomyślne zakończenie lub przez błąd), jest zamykany.

Chociaż myślę, że lepiej byłoby to zorganizować w ten sposób, ponieważ znacznie łatwiej jest zobaczyć, co się stanie, nawet dla nowego programisty utrzymania ruchu, który wesprze to później:

using (SqlConnection connection = new SqlConnection(connectionString)) 
{    
    int employeeID = findEmployeeID();    
    try    
    {
        connection.Open();
        SqlCommand command = new SqlCommand("UpdateEmployeeTable", connection);
        command.CommandType = CommandType.StoredProcedure;
        command.Parameters.Add(new SqlParameter("@EmployeeID", employeeID));
        command.CommandTimeout = 5;

        command.ExecuteNonQuery();    
    } 
    catch (Exception) 
    { 
        /*Handle error*/ 
    }
}
David
źródło
3
@TrueWill - zgadzam się. Po prostu trochę przesunąłem kod ze względu na strukturę.
David,
10
Pytanie: Czy muszę nawet OTWIERAĆ połączenie, używając instrukcji Using?
Fandango68
3
Również jeśli korzystasz z transakcji, mając try catchwewnątrz usingmożesz jawnie .Commitlub .Rollbacktransakcje w catch. Jest to zarówno bardziej czytelne, jak i wyraźne, i pozwala na zatwierdzenie, jeśli ma to sens, biorąc pod uwagę typ wyjątku. (Transakcje niejawnie wycofują się, conn.Closejeśli nie zostały zatwierdzone).
Chris
8
@ Fernando68 Tak, nadal masz Openpołączenie. usinggwarantuje tylko, że Disposezostanie wywołana metoda obiektu .
juharr
Mam zwrot ExecuteScalar wewnątrz przy użyciu bloków. A kiedy uruchamiam tę metodę po raz drugi, jest to bardzo szybkie, jakby połączenie było otwarte. Dlaczego za drugim razem jest tak szybko?
pozytywna perspektywa
46

Tak na oba pytania. Instrukcja using zostaje skompilowana do bloku try / final

using (SqlConnection connection = new SqlConnection(connectionString))
{
}

jest taki sam jak

SqlConnection connection = null;
try
{
    connection = new SqlConnection(connectionString);
}
finally
{
   if(connection != null)
        ((IDisposable)connection).Dispose();
}

Edycja: Naprawianie rzutowania na jednorazowe http://msdn.microsoft.com/en-us/library/yh598w02.aspx

Ryan Pedersen
źródło
to nie jest dokładnie to, ale jest wystarczająco blisko. dokładna różnica nie jest ważna.
Bryan,
@Bryan tego nie rozumiał, czy możesz podać dokładną różnicę, może pomóc nam bardziej się oprzeć :-)
mohits00691
Wow, to był komentarz zrobiony dawno temu :) Wygląda na to, że dzień po tym komentarzu była zmiana. Myślę, że to jest różnica, o której myślałem.
Bryan,
@Bryan Tak, naprawiłem dokonaną korektę po Twoim komentarzu.
Ryan Pedersen
17

Oto mój szablon. Wszystko, czego potrzebujesz, aby wybrać dane z serwera SQL. Połączenie jest zamykane i usuwane, a błędy w połączeniu i wykonywaniu są wychwytywane.

string connString = System.Configuration.ConfigurationManager.ConnectionStrings["CompanyServer"].ConnectionString;
string selectStatement = @"
    SELECT TOP 1 Person
    FROM CorporateOffice
    WHERE HeadUpAss = 1 AND Title LIKE 'C-Level%'
    ORDER BY IntelligenceQuotient DESC
";
using (SqlConnection conn = new SqlConnection(connString))
{
    using (SqlCommand comm = new SqlCommand(selectStatement, conn))
    {
        try
        {
            conn.Open();
            using (SqlDataReader dr = comm.ExecuteReader())
            {
                if (dr.HasRows)
                {
                    while (dr.Read())
                    {
                        Console.WriteLine(dr["Person"].ToString());
                    }
                }
                else Console.WriteLine("No C-Level with Head Up Ass Found!? (Very Odd)");
            }
        }
        catch (Exception e) { Console.WriteLine("Error: " + e.Message); }
        if (conn.State == System.Data.ConnectionState.Open) conn.Close();
    }
}

* Poprawiono: 2015-11-09 *
Zgodnie z sugestią NickG; Jeśli denerwuje Cię zbyt wiele aparatów ortodontycznych, sformatuj w ten sposób ...

using (SqlConnection conn = new SqlConnection(connString))
   using (SqlCommand comm = new SqlCommand(selectStatement, conn))
   {
      try
      {
         conn.Open();
         using (SqlDataReader dr = comm.ExecuteReader())
            if (dr.HasRows)
               while (dr.Read()) Console.WriteLine(dr["Person"].ToString());
            else Console.WriteLine("No C-Level with Head Up Ass Found!? (Very Odd)");
      }
      catch (Exception e) { Console.WriteLine("Error: " + e.Message); }
      if (conn.State == System.Data.ConnectionState.Open) conn.Close();
   }

Z drugiej strony, jeśli pracujesz dla gier EA lub DayBreak, możesz po prostu zrezygnować z przerw między wierszami, ponieważ są one przeznaczone tylko dla osób, które muszą później wrócić i spojrzeć na Twój kod, a kogo to naprawdę obchodzi? Czy mam rację? To znaczy 1 linia zamiast 23 oznacza, że ​​jestem lepszym programistą, prawda?

using (SqlConnection conn = new SqlConnection(connString)) using (SqlCommand comm = new SqlCommand(selectStatement, conn)) { try { conn.Open(); using (SqlDataReader dr = comm.ExecuteReader()) if (dr.HasRows) while (dr.Read()) Console.WriteLine(dr["Person"].ToString()); else Console.WriteLine("No C-Level with Head Up Ass Found!? (Very Odd)"); } catch (Exception e) { Console.WriteLine("Error: " + e.Message); } if (conn.State == System.Data.ConnectionState.Open) conn.Close(); }

Uff ... OK. Wyciągnąłem to z mojego systemu i przez chwilę się bawię. Kontynuować.

ShaneLS
źródło
6
Czy wiesz, że możesz nakładać instrukcje bez dodatkowych nawiasów klamrowych? Usuń ostatni nawias, a następnie umieść instrukcje using obok siebie :)
NickG,
Tak jest. Dziękuję Ci. Jestem świadomy, ale chciałem, aby mój kod dokładnie pokazywał, co się dzieje, bez używania zbyt wielu innych skrótów. Dobra uwaga do dodania do czytelników końcowych.
ShaneLS
Dlaczego używasz conn.Close();na końcu? Czy usingoświadczenie nie robi tego za Ciebie poprzez utylizację?
Fredrick Gauss
Myślę, że teraz (od .net 3.5). Było to dla mnie niejasne na początku .net 2.0, więc po prostu przyzwyczaiłem się do sprawdzania i zamykania.
ShaneLS
1
„średnia 1 linia zamiast 23 oznacza, że ​​jestem lepszym programistą, prawda?” Lubię cię :-D
Philipp Müller
5

Dispose zostaje po prostu wywołane, gdy opuścisz zakres using. Celem „używania” jest zapewnienie programistom zagwarantowanego sposobu na pozbycie się zasobów.

Z MSDN :

Instrukcja using może zostać zakończona, gdy zostanie osiągnięty koniec instrukcji using lub jeśli zostanie zgłoszony wyjątek, a sterowanie pozostawi blok instrukcji przed końcem instrukcji.

overstood
źródło
5

Usinggeneruje próbę / w końcu wokół przydzielanego obiektu i woła Dispose()do Ciebie.

Oszczędza to kłopotów związanych z ręcznym tworzeniem bloku try / last i dzwonieniem Dispose()

VoodooChild
źródło
3

W pierwszym przykładzie kompilator C # faktycznie przetłumaczy instrukcję using na następującą:

SqlConnection connection = new SqlConnection(connectionString));

try
{
    connection.Open();

    string storedProc = "GetData";
    SqlCommand command = new SqlCommand(storedProc, connection);
    command.CommandType = CommandType.StoredProcedure;
    command.Parameters.Add(new SqlParameter("@EmployeeID", employeeID));

    return (byte[])command.ExecuteScalar();
}
finally
{
    connection.Dispose();
}

Instrukcje Final zawsze będą wywoływane przed powrotem funkcji, więc połączenie będzie zawsze zamykane / usuwane.

Tak więc w drugim przykładzie kod zostanie skompilowany w następujący sposób:

try
{
    try
    {
        connection.Open();

        string storedProc = "GetData";
        SqlCommand command = new SqlCommand(storedProc, connection);
        command.CommandType = CommandType.StoredProcedure;
        command.Parameters.Add(new SqlParameter("@EmployeeID", employeeID));

        return (byte[])command.ExecuteScalar();
    }
    finally
    {
        connection.Dispose();
    }
}
catch (Exception)
{
}

Wyjątek zostanie przechwycony w instrukcji last i połączenie zostanie zamknięte. Wyjątek nie będzie widoczny w klauzuli zewnętrznej catch.

Kerri Brown
źródło
1
bardzo dobre przykłady człowieku, ale muszę się nie zgodzić z twoim ostatnim komentarzem, jeśli wyjątek wystąpi w bloku using, zostanie złapany bez problemów na dowolnym zewnętrznym zaczepie, w rzeczywistości przetestowałem to, pisząc 2 używając bloków w bloku try / catch i ku mojemu zdziwieniu otrzymałem komunikat o błędzie wyjątku, który pochodzi z wewnętrznej sekundy przy użyciu bloku.
WhySoSerious,
1

Napisałem dwie instrukcje using wewnątrz bloku try / catch i mogłem zobaczyć, że wyjątek jest przechwytywany w ten sam sposób, jeśli jest umieszczony w wewnętrznej instrukcji using , tak jak w przykładzie ShaneLS .

     try
     {
       using (var con = new SqlConnection(@"Data Source=..."))
       {
         var cad = "INSERT INTO table VALUES (@r1,@r2,@r3)";

         using (var insertCommand = new SqlCommand(cad, con))
         {
           insertCommand.Parameters.AddWithValue("@r1", atxt);
           insertCommand.Parameters.AddWithValue("@r2", btxt);
           insertCommand.Parameters.AddWithValue("@r3", ctxt);
           con.Open();
           insertCommand.ExecuteNonQuery();
         }
       }
     }
     catch (Exception ex)
     {
       MessageBox.Show("Error: " + ex.Message, "UsingTest", MessageBoxButtons.OK, MessageBoxIcon.Error);
     }

Bez względu na to, gdzie jest umieszczony tryb try / catch , wyjątek zostanie przechwycony bez problemów.

Dlaczego tak poważnie
źródło