en un bloque "en uso", ¿se cierra una conexión SqlConnection en caso de devolución o excepción?

136

Primera pregunta:
digamos que tengo

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

¿Se cierra la conexión? Porque técnicamente nunca llegamos a lo último }como lo hicimos returnantes.

Segunda pregunta:
esta vez tengo:

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*/ }

Ahora, digamos que en algún lugar en el tryrecibimos un error y se detecta. ¿La conexión todavía se cierra? Porque, de nuevo, omitimos el resto del código en el tryy vamos directamente a la catchdeclaración.

¿Estoy pensando demasiado linealmente en cómo usingfunciona? es decir, ¿ Dispose()simplemente se llama cuando dejamos el usingalcance?

Marcus
fuente

Respuestas:

178
  1. si
  2. Si.

De cualquier manera, cuando se sale del bloque de uso (ya sea por finalización exitosa o por error), se cierra.

Aunque creo que sería mejor organizarse de esta manera porque es mucho más fácil ver lo que sucederá, incluso para el nuevo programador de mantenimiento que lo respaldará más adelante:

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
fuente
3
@TrueWill - Estoy de acuerdo. Acabo de mover el código un poco para la estructura.
David
10
Pregunta: ¿Necesito incluso ABRIR una conexión cuando uso la instrucción Usar?
Fandango68
3
Además, si usted está usando transacciones, haciendo que el try catchplazo de la usingque pueda de manera explícita .Commito .Rollbacktransacciones en el catch. Esto es más legible y explícito, y le permite confirmar si eso tiene sentido dado el tipo de excepción. (Las transacciones se retrotraen implícitamente conn.Closesi no se confirman).
Chris
8
@ Fernando68 Sí, todavía tienes que Openconectar. usingsolo garantiza que Disposese llama al método del objeto .
juharr
Tengo el retorno ExecuteScalar dentro usando bloques. Y cuando ejecuto el método por segunda vez, es muy rápido, como si la conexión estuviera abierta. ¿Por qué es tan rápido la segunda vez?
perspectiva positiva
46

Sí a las dos preguntas. La instrucción using se compila en un bloque try / finally

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

es lo mismo que

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

Editar: Arreglando el elenco en Desechable http://msdn.microsoft.com/en-us/library/yh598w02.aspx

Ryan Pedersen
fuente
no es exactamente eso, pero está lo suficientemente cerca. La diferencia exacta no es importante.
Bryan
@Bryan no lo entendió, ¿puede mencionar la diferencia exacta, puede ayudarnos a inclinarnos más :-)
mohits00691
Wow, ese fue un comentario hecho hace mucho tiempo :) Parece que hubo una edición el día después de que hice ese comentario. Creo que esa es la diferencia en la que estaba pensando.
Bryan
@Bryan Sí, arreglé el ajuste después de tu comentario.
Ryan Pedersen
17

Aquí está mi plantilla. Todo lo que necesita para seleccionar datos de un servidor SQL. La conexión se cierra y se elimina y se detectan errores en la conexión y la ejecución.

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

* Revisado: 09/11/2015 *
Según lo sugerido por NickG; Si le molestan demasiadas llaves, formatee así ...

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

Por otra parte, si trabajas para juegos EA o DayBreak, también puedes renunciar a cualquier salto de línea porque son solo para personas que tienen que volver y ver tu código más tarde y a quién realmente le importa. Estoy en lo cierto? Quiero decir 1 línea en lugar de 23 significa que soy un mejor programador, ¿verdad?

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

Uf ... ok. Lo saqué de mi sistema y terminé de divertirme por un tiempo. Continua.

ShaneLS
fuente
66
¿Sabía que puede apilar usando declaraciones sin llaves adicionales? Elimine la última llave, luego coloque las declaraciones de uso una al lado de la otra :)
NickG
Sí señor. Gracias. Soy consciente, pero quería que mi código mostrara exactamente lo que estaba sucediendo sin usar demasiados atajos. Sin embargo, es una buena nota para agregar a los lectores finales.
ShaneLS
¿Por qué usas conn.Close();al final? ¿La usingdeclaración no hace eso por ti a través de la eliminación?
Fredrick Gauss
Creo que lo hace ahora (desde .net 3.5). No estaba claro para mí al principio con .net 2.0, así que simplemente hice un hábito verificar y cerrar.
ShaneLS
1
"significa 1 línea en lugar de 23 significa que soy un mejor programador, ¿verdad?" Me gustas :-D
Philipp Müller
5

Deseche simplemente recibe una llamada cuando abandona el alcance del uso. La intención de "usar" es dar a los desarrolladores una forma garantizada de asegurarse de que los recursos se eliminen.

De MSDN :

Una declaración de uso se puede salir cuando se alcanza el final de la declaración de uso o si se produce una excepción y el control abandona el bloque de la declaración antes del final de la declaración.

entendido
fuente
5

Usinggenera un try / finalmente alrededor del objeto que se está asignando y lo llama Dispose()por usted.

Le ahorra la molestia de crear manualmente el bloque try / finalmente y llamar Dispose()

Niño Voodoo
fuente
3

En su primer ejemplo, el compilador de C # traducirá la declaración de uso a lo siguiente:

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

Finalmente, siempre se llamará a las declaraciones antes de que regrese una función, por lo que la conexión siempre se cerrará / eliminará.

Entonces, en su segundo ejemplo, el código se compilará de la siguiente manera:

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)
{
}

La excepción quedará atrapada en la declaración final y se cerrará la conexión. La excepción no se verá en la cláusula de captura externa.

Kerri Brown
fuente
1
muy buenos ejemplos hombre, pero tengo que estar en desacuerdo con su último comentario, si ocurre una excepción dentro de un bloque de uso, se detectará sin problemas en cualquier captura externa, de hecho, lo probé escribiendo 2 bloques de uso dentro de un bloque try / catch , y para mi sorpresa, recibí mi mensaje de error de excepción que vino del segundo interior usando el bloque.
WhySoSerious
1

Escribí dos declaraciones de uso dentro de un bloque try / catch y pude ver que la excepción se detectaba de la misma manera si se coloca dentro de la declaración de uso interno como en el ejemplo de 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);
     }

No importa dónde se coloque el try / catch , la excepción se detectará sin problemas.

Por qué tan serio
fuente