É necessário fechar e descartar manualmente o SqlDataReader?

90

Estou trabalhando com código legado aqui e há muitas instâncias SqlDataReaderque nunca são fechadas ou descartadas. A conexão está fechada, mas não tenho certeza se é necessário gerenciar o leitor manualmente.

Isso pode causar uma desaceleração no desempenho?

Jon Ownbey
fonte

Respostas:

124

Tente evitar o uso de leitores como este:

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

Em vez disso, envolva-os em instruções:

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

A instrução using irá garantir o descarte correto do objeto e a liberação de recursos.

Se você esquecer, está deixando a limpeza para o coletor de lixo, o que pode demorar um pouco.

Codebrain
fonte
24
Você não precisa da instrução .Close () em nenhum dos exemplos: ela é tratada pela chamada .Dispose ().
Joel Coehoorn
7
Provavelmente deseja verificar se .HasRows em vez de null.
JonH
3
@Andrew Se ExecuteReader lançar uma exceção, como ele pode retornar nulo?
csauve
7
@JohH: o while (reader.Read ()) no exemplo realiza o mesmo que .HasRows, e você precisa .Read de qualquer maneira para avançar o leitor para a primeira linha.
csauve
1
@csauve Você está certo, acho que não deve ter retornado null. Não sei por que estava olhando para o valor da variável SqlDataReader.
Andrew
53

Observe que descartar um SqlDataReader instanciado usando SqlCommand.ExecuteReader () não fechará / descartará a conexão subjacente.

Existem dois padrões comuns. Na primeira, o leitor é aberto e fechado no âmbito da conexão:

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

Às vezes, é conveniente ter um método de acesso a dados para abrir uma conexão e retornar um leitor. Nesse caso, é importante que o leitor retornado seja aberto usando CommandBehavior.CloseConnection, para que fechar / descartar o leitor feche a conexão subjacente. O padrão é mais ou menos assim:

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

e o código de chamada precisa apenas dispor o leitor assim:

using(SqlDataReader reader = ExecuteReader(...))
{
    ... do your stuff ...
} // reader and connection are closed here.
Joe
fonte
No segundo trecho de código, em que o método retorna um SqlDataReader, o comando não é descartado. Tudo bem para descartar o comando (incluí-lo em um bloco de uso) e retornar o leitor?
alwayslearning
@alwayslearning esse é exatamente o cenário que eu tenho ...... você pode fechar / descartar o SqlCommand quando estiver retornando o SqlDataReader para o chamador?
ganders
1
Isto é mau. Se você REALMENTE não aguentar usar usings, chame dispose no finally {}bloco após a captura. Da forma como isso é escrito, comandos bem-sucedidos nunca seriam fechados ou descartados.
smdrager
2
@smdrager, se você leu a resposta mais de perto, ele está falando sobre um método que retorna um leitor. Se você usar .ExecuteReader (CommandBehavior.CloseConnection); então, eliminando o READER, a conexão será fechada. Portanto, o método de chamada precisa apenas envolver o leitor resultante em uma instrução using. using (var rdr = SqlHelper.GetReader ()) {// ...} se você o fechasse no bloco finally, seu leitor não conseguiria ler porque a conexão foi fechada.
Sinestésico
@ganders - voltando a este post antigo: sim, você pode e provavelmente deve descartar o SqlCommand - atualizou o exemplo para fazer isso.
Joe
11

Para ser seguro, envolva cada objeto SqlDataReader em uma instrução using .

Kon
fonte
Justo. No entanto, isso realmente faz diferença no desempenho se não houver instrução de uso?
Jon Ownbey
Uma instrução using é o mesmo que envolver o código DataReader em um bloco try..finally ..., com o método close / dispose na seção finally. Basicamente, ele apenas "garante" que o objeto será descartado de maneira adequada.
Todd,
Vem direto do link que forneci: "A instrução using garante que Dispose seja chamado mesmo se ocorrer uma exceção enquanto você chama métodos no objeto."
Kon
5
Continuação ... "Você pode obter o mesmo resultado colocando o objeto dentro de um bloco try e então chamando Dispose em um bloco finally; na verdade, é assim que a instrução using é traduzida pelo compilador."
Kon
5

Apenas envolva seu SQLDataReader com a instrução "using". Isso deve cuidar da maioria dos seus problemas.

JW
fonte