Est-il nécessaire de fermer et de supprimer manuellement SqlDataReader?

90

Je travaille avec du code hérité ici et il existe de nombreuses instances SqlDataReaderqui ne sont jamais fermées ou supprimées. La connexion est fermée mais, je ne suis pas sûr qu'il soit nécessaire de gérer le lecteur manuellement.

Cela pourrait-il entraîner un ralentissement des performances?

Jon Ownbey
la source

Réponses:

124

Essayez d'éviter d'utiliser des lecteurs comme celui-ci:

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

Au lieu de cela, enveloppez-les dans des instructions d'utilisation:

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 déclaration using assurera l'élimination correcte de l'objet et la libération des ressources.

Si vous oubliez, vous laissez le nettoyage au ramasse-miettes, ce qui peut prendre un certain temps.

Codebrain
la source
24
Vous n'avez besoin de l'instruction .Close () dans aucun des deux exemples: elle est gérée par l'appel .Dispose ().
Joel Coehoorn
7
Vous voulez probablement vérifier si c'est .HasRows plutôt que null.
JonH
3
@Andrew Si ExecuteReader lève une exception, comment peut-il retourner null?
csauve
7
@JohH: le while (reader.Read ()) dans l'exemple accomplit la même chose que .HasRows, et vous devez quand même .Lire pour avancer le lecteur vers la première ligne.
csauve
1
@csauve Vous avez raison, je suppose qu'il ne doit pas avoir retourné null. Je ne sais pas pourquoi je regardais la valeur de la variable SqlDataReader.
Andrew
53

Notez que la suppression d'un SqlDataReader instancié à l'aide de SqlCommand.ExecuteReader () ne fermera / supprimera pas la connexion sous-jacente.

Il existe deux modèles communs. Dans le premier, le lecteur est ouvert et fermé dans le cadre de la connexion:

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

Parfois, il est pratique qu'une méthode d'accès aux données ouvre une connexion et renvoie un lecteur. Dans ce cas, il est important que le lecteur renvoyé soit ouvert à l'aide de CommandBehavior.CloseConnection, afin que la fermeture / suppression du lecteur ferme la connexion sous-jacente. Le motif ressemble à ceci:

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

et le code appelant a juste besoin de disposer le lecteur ainsi:

using(SqlDataReader reader = ExecuteReader(...))
{
    ... do your stuff ...
} // reader and connection are closed here.
Joe
la source
Dans le deuxième extrait de code où la méthode renvoie un SqlDataReader, la commande n'est pas supprimée. Est-ce correct et est-il correct de supprimer la commande (placez-la dans un bloc using) puis de renvoyer le lecteur?
alwayslearning
@alwayslearning c'est exactement le scénario que j'ai ...... pouvez-vous fermer / supprimer le SqlCommand lorsque vous renvoyez le SqlDataReader à l'appelant?
ganders
1
C'est mauvais. Si vous ne pouvez VRAIMENT pas supporter d'utiliser usings, alors appelez dispose dans le finally {}bloc après catch. De la manière dont cela est écrit, les commandes réussies ne seront jamais fermées ou supprimées.
smdrager
2
@smdrager, si vous lisez la réponse de plus près, il parle d'une méthode qui renvoie un lecteur. Si vous utilisez .ExecuteReader (CommandBehavior.CloseConnection); puis en supprimant le READER, la connexion sera fermée. Ainsi, la méthode d'appel n'a besoin que d'encapsuler le lecteur résultant dans une instruction using. en utilisant (var rdr = SqlHelper.GetReader ()) {// ...} si vous le fermiez dans le bloc finally, alors votre lecteur échouerait à lire car la connexion est fermée.
Sinaesthetic
@ganders - pour revenir à cet ancien post: oui, vous pouvez et devriez probablement supprimer SqlCommand - a mis à jour l'exemple pour le faire.
Joe
11

Pour être sûr, enveloppez chaque objet SqlDataReader dans une instruction using .

Kon
la source
C'est suffisant. Cependant, cela fait-il réellement une différence de performances s'il n'y a pas d'instruction using?
Jon Ownbey
Une instruction using revient à encapsuler le code DataReader dans un bloc try..finally ..., avec la méthode close / dispose dans la section finally. Fondamentalement, il "garantit" simplement que l'objet sera éliminé correctement.
Todd
Ceci vient directement du lien que j'ai fourni: "L'instruction using garantit que Dispose est appelé même si une exception se produit pendant que vous appelez des méthodes sur l'objet."
Kon
5
Suite ... "Vous pouvez obtenir le même résultat en plaçant l'objet dans un bloc try puis en appelant Dispose dans un bloc finally; en fait, c'est ainsi que l'instruction using est traduite par le compilateur."
Kon
5

Enveloppez simplement votre SQLDataReader avec l'instruction "using". Cela devrait régler la plupart de vos problèmes.

JW
la source