¿Es necesario cerrar y eliminar manualmente SqlDataReader?

90

Estoy trabajando con código heredado aquí y hay muchos casos SqlDataReaderque nunca se cierran o eliminan. La conexión está cerrada pero no estoy seguro si es necesario administrar el lector manualmente.

¿Podría esto provocar una ralentización del rendimiento?

Jon Ownbey
fuente

Respuestas:

125

Trate de evitar el uso de lectores 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

En su lugar, envuélvalos en declaraciones de uso:

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

La declaración de uso asegurará la correcta eliminación del objeto y la liberación de recursos.

Si lo olvidas, dejarás la limpieza en manos del recolector de basura, lo que podría llevar un tiempo.

Codebrain
fuente
24
No necesita la instrucción .Close () en ninguna de las muestras: es manejada por la llamada .Dispose ().
Joel Coehoorn
7
Probablemente quiera verificar si tiene .HasRows en lugar de null.
JonH
3
@Andrew Si ExecuteReader lanza una excepción, ¿cómo puede devolver un valor nulo?
Csauve el
7
@JohH: el while (reader.Read ()) en el ejemplo logra lo mismo que .HasRows, y necesita .Read de todos modos para hacer avanzar al lector a la primera fila.
Csauve el
1
@csauve Tienes razón, supongo que no debe haber devuelto nulo. No estoy seguro de por qué estaba mirando el valor de la variable SqlDataReader.
Andrew
54

Tenga en cuenta que eliminar un SqlDataReader instanciado mediante SqlCommand.ExecuteReader () no cerrará / eliminará la conexión subyacente.

Hay dos patrones comunes. En el primero, el lector se abre y se cierra dentro del alcance de la conexión:

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

A veces es conveniente tener un método de acceso a datos para abrir una conexión y devolver un lector. En este caso, es importante que el lector devuelto se abra usando CommandBehavior.CloseConnection, de modo que cerrar / eliminar el lector cerrará la conexión subyacente. El patrón se parece a esto:

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

y el código de llamada solo necesita disponer al lector de esta manera:

using(SqlDataReader reader = ExecuteReader(...))
{
    ... do your stuff ...
} // reader and connection are closed here.
Joe
fuente
En el segundo fragmento de código donde el método devuelve un SqlDataReader, el comando no se elimina. ¿Está bien y está bien deshacerse del comando (encerrarlo en un bloque de uso) y luego devolver el lector?
alwayslearning
@alwayslearning ese es exactamente el escenario que tengo ... ¿puede cerrar / deshacerse del SqlCommand cuando devuelve el SqlDataReader a la persona que llama?
ganders
1
Esto es malo. Si REALMENTE no puedes soportar usar usings, entonces llama a dispose en el finally {}bloque después de la captura. De la forma en que esto está escrito, los comandos exitosos nunca se cerrarán o eliminarán.
smdrager
3
@smdrager, si lee la respuesta más de cerca, está hablando de un método que devuelve un lector. Si usa .ExecuteReader (CommandBehavior.CloseConnection); luego, al desechar el LECTOR, la conexión se cerrará. Entonces, el método de llamada solo necesita envolver el lector resultante en una declaración de uso. using (var rdr = SqlHelper.GetReader ()) {// ...} si lo cerrara en el bloque finalmente, su lector no podría leer porque la conexión está cerrada.
Sinaesthetic
@ganders: volviendo a esta publicación anterior: sí, puede y probablemente debería deshacerse del SqlCommand: actualizó el ejemplo para hacerlo.
Joe
11

Para estar seguro, envuelva cada objeto SqlDataReader en una instrucción using .

Kon
fuente
Lo suficientemente justo. Sin embargo, ¿realmente hace una diferencia en el rendimiento si no hay una declaración de uso?
Jon Ownbey
Una declaración using es lo mismo que envolver el código de DataReader en un bloque try..finally ..., con el método close / dispose en la sección finalmente. Básicamente, simplemente "garantiza" que el objeto se eliminará correctamente.
Todd
1
Esto es directamente del enlace que proporcioné: "La declaración using garantiza que se llame a Dispose incluso si se produce una excepción mientras llama a métodos en el objeto".
Kon
6
Continuación ... "Puede lograr el mismo resultado colocando el objeto dentro de un bloque try y luego llamando a Dispose en un bloque finalmente; de ​​hecho, así es como el compilador traduce la instrucción using".
Kon
5

Simplemente envuelva su SQLDataReader con la declaración "using". Eso debería solucionar la mayoría de sus problemas.

JW
fuente