em um bloco "using" um SqlConnection é fechado no retorno ou exceção?

136

Primeira pergunta:
diga que eu tenho

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

A conexão é fechada? Porque tecnicamente nunca chegamos ao último }como returnantes.

Segunda pergunta:
Desta vez eu tenho:

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

Agora, digamos que, em algum lugar try, recebemos um erro e ele seja capturado. A conexão ainda está fechada? Porque, novamente, pulamos o restante do código no trye vamos diretamente para a catchinstrução

Estou pensando linearmente demais em como usingfunciona? ou seja, Dispose()simplesmente é chamado quando deixamos o usingescopo?

Marcus
fonte

Respostas:

178
  1. sim
  2. Sim.

De qualquer forma, quando o bloco de uso é encerrado (por conclusão bem-sucedida ou por erro), ele é fechado.

Embora eu ache que seria melhor organizar assim, porque é muito mais fácil ver o que vai acontecer, mesmo para o novo programador de manutenção que o apoiará mais tarde:

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
fonte
3
@TrueWill - eu concordo. Acabei de mudar o código um pouco para a estrutura.
David
10
Pergunta: Eu preciso ABRIR uma conexão ao usar a instrução Using?
Fandango68
3
Além disso, se você estiver usando transações, tendo o try catchdentro do usingvocê pode explicitamente .Commitou .Rollbacktransações no catch. Isso é mais legível e explícito e permite que você confirme se isso faz sentido, devido ao tipo da exceção. (As transações implicitamente são revertidas conn.Closese não confirmadas.).
Chris
8
@ Fernando68 Sim, você ainda tem Opena conexão. usingapenas garante que o Disposemétodo do objeto seja chamado.
juharr
Eu tenho o retorno ExecuteScalar dentro usando blocos. E quando executo o método pela segunda vez, é muito rápido, como se a conexão estivesse aberta. Por que é tão rápido na segunda vez?
perspectiva positiva
46

Sim para ambas as perguntas. A instrução using é compilada em um bloco try / finalmente

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

é o mesmo que

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

Edit: Fixing the cast to Disposable http://msdn.microsoft.com/en-us/library/yh598w02.aspx

Ryan Pedersen
fonte
não é exatamente isso, mas está perto o suficiente. a diferença exata não é importante.
Bryan
@Bryan não obtê-lo, você pode por favor mencione a diferença exata, pode nos ajudar a inclinar-se mais :-)
mohits00691
Uau, isso foi um comentário feito há muito tempo :) Parece que houve uma edição no dia seguinte ao meu comentário. Eu acho que é a diferença que eu estava pensando.
21712 Bryan
@ Bryan Sim, eu consertei feito o ajuste após o seu comentário.
22612 Ryan Pedersen
17

Aqui está o meu modelo. Tudo o que você precisa para selecionar dados de um servidor SQL. A conexão é fechada e descartada e erros na conexão e execução são detectados.

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 *
Conforme sugerido por NickG; Se muitas chaves lhe incomodam, formate assim ...

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 outro lado, se você trabalha para jogos da EA ou do DayBreak, pode simplesmente renunciar a qualquer quebra de linha, porque são apenas para pessoas que precisam voltar e olhar seu código mais tarde e quem realmente se importa? Estou certo? Quero dizer, 1 linha em vez de 23 significa que sou um programador melhor, certo?

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

Ufa ... OK. Tirei isso do meu sistema e acabei me divertindo por um tempo. Continue.

ShaneLS
fonte
6
Você sabia que pode empilhar usando instruções sem chaves adicionais? Suprimir a última cinta, em seguida, coloque as declarações usando ao lado do outro :)
NickG
Sim senhor. Obrigado. Estou ciente, mas queria que meu código mostrasse exatamente o que estava acontecendo sem usar muitos outros atalhos. Boa nota para adicionar aos leitores finais.
ShaneLS
Por que você usa conn.Close();no final? A usingdeclaração não faz isso por você através do descarte?
Fredrick Gauss
Eu acredito que sim agora (desde .net 3.5). Não estava claro para mim desde o início com o .net 2.0, então eu criei o hábito de verificar e fechar.
ShaneLS
1
"significa 1 linha em vez de 23 significa que eu sou um programador melhor, certo?" Eu gosto de você :-D
Philipp Müller
5

Dispose simplesmente é chamado quando você sai do escopo de uso. A intenção de "usar" é oferecer aos desenvolvedores uma maneira garantida de garantir que os recursos sejam descartados.

Do MSDN :

Uma instrução using pode ser encerrada quando o final da instrução using for atingido ou se uma exceção for lançada e o controle deixar o bloco de instruções antes do final da instrução.

ultrapassado
fonte
5

Usinggera uma tentativa / finalmente ao redor do objeto que está sendo alocado e chama Dispose()por você.

Isso evita o incômodo de criar manualmente o bloco try / finalmente e chamar Dispose()

Criança Voodoo
fonte
3

No seu primeiro exemplo, o compilador C # realmente converterá a instrução using para o seguinte:

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, as instruções sempre serão chamadas antes do retorno de uma função e, portanto, a conexão será sempre fechada / descartada.

Portanto, no seu segundo exemplo, o código será compilado da seguinte maneira:

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

A exceção será capturada na instrução finalmente e a conexão será fechada. A exceção não será vista pela cláusula de captura externa.

Kerri Brown
fonte
1
exemplos muito bons, cara, mas eu tenho que discordar do seu último comentário, se uma exceção ocorrer dentro de um bloco de uso, ela será capturada sem problemas em qualquer captura externa, na verdade eu testei escrevendo 2 usando blocos dentro de um bloco de tentativa / captura e, para minha surpresa, recebi minha mensagem de erro de exceção que veio do segundo interior usando o bloco.
WhySoSerious
1

Escrevi duas instruções using dentro de um bloco try / catch e pude ver que a exceção estava sendo capturada da mesma maneira, se colocada dentro da instrução inner using , como no exemplo do 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);
     }

Não importa onde a tentativa / captura seja feita, a exceção será capturada sem problemas.

Porquê tão sério
fonte