130 votes

ExecuteReader nécessite une connexion ouverte et disponible. L'état actuel de la connexion est Connecter.

Lorsque j'essaie de me connecter à la base de données MSSQL via ASP.NET en ligne, j'obtiens ce message lorsque deux personnes ou plus se connectent simultanément :

ExecuteReader nécessite une connexion ouverte et disponible. L'état actuel de la connexion est Connecting.

Le site fonctionne bien sur mon serveur local.

Voici le code approximatif.

public Promotion retrievePromotion()
{
    int promotionID = 0;
    string promotionTitle = "";
    string promotionUrl = "";
    Promotion promotion = null;
    SqlOpenConnection();
    SqlCommand sql = SqlCommandConnection();

    sql.CommandText = "SELECT TOP 1 PromotionID, PromotionTitle, PromotionURL FROM Promotion";

    SqlDataReader dr = sql.ExecuteReader();
    while (dr.Read())
    {
        promotionID = DB2int(dr["PromotionID"]);
        promotionTitle = DB2string(dr["PromotionTitle"]);
        promotionUrl = DB2string(dr["PromotionURL"]);
        promotion = new Promotion(promotionID, promotionTitle, promotionUrl);
    }
    dr.Dispose();
    sql.Dispose();
    CloseConnection();
    return promotion;
}

Pourriez-vous me dire ce qui a pu mal tourner et comment puis-je le corriger ?

Édition : N'oubliez pas que ma chaîne de connexion et la connexion sont toutes deux statiques. Je crois que c'est la raison. Merci de votre conseil.

public static string conString = ConfigurationManager.ConnectionStrings["dbConnection"].ConnectionString;
public static SqlConnection conn = null;

25 votes

Ne pas utiliser de connexions partagées/statiques dans un environnement multithread comme ASP.NET car cela générera des verrous ou des exceptions (trop de connexions ouvertes, etc.). Jetez votre classe DB à la poubelle et créez, ouvrez, utilisez, fermez, supprimez des objets ado.net où vous en avez besoin. Jetez un œil également à l'instruction using.

2 votes

Pouvez-vous me donner des détails sur les fonctions SqlOpenConnection (); et sql.ExecuteReader ();?..

0 votes

Private void SqlOpenConnection() { try { conn = new SqlConnection(); conn.ConnectionString = conString; conn.Open(); } catch (SqlException ex) { throw ex; } }

246voto

Tim Schmelter Points 163781

Désolé de ne commenter qu'à la base, mais je poste presque tous les jours un commentaire similaire depuis que de nombreuses personnes pensent qu'il serait intelligent d'encapsuler la fonctionnalité ADO.NET dans une classe DB (moi aussi, il y a 10 ans). La plupart du temps, ils décident d'utiliser des objets statiques/partagés car il semble que ce soit plus rapide que de créer un nouvel objet pour chaque action.

Ce n'est pas une bonne idée en termes de performances ni en termes de fiabilité.

Ne pas empiéter sur le territoire du pool de connexions

Il y a une bonne raison pour laquelle ADO.NET gère internalement les connexions sous-jacentes au SGBD dans le Pool de connexions ADO-NET:

En pratique, la plupart des applications n'utilisent qu'une ou quelques configurations différentes de connexions. Cela signifie que pendant l'exécution de l'application, de nombreuses connexions identiques seront ouvertes et fermées à plusieurs reprises. Pour minimiser le coût de l'ouverture des connexions, ADO.NET utilise une technique d'optimisation appelée pool de connexions.

Le pool de connexions réduit le nombre de fois que de nouvelles connexions doivent être ouvertes. Le gestionnaire de pool conserve la propriété de la connexion physique. Il gère les connexions en maintenant un ensemble de connexions actives pour chaque configuration de connexion donnée. Chaque fois qu'un utilisateur appelle Open sur une connexion, le gestionnaire de pool recherche une connexion disponible dans le pool. Si une connexion mise en pool est disponible, il la renvoie à l'appelant au lieu d'ouvrir une nouvelle connexion. Lorsque l'application appelle Close sur la connexion, le gestionnaire de pool la renvoie à l'ensemble mis en pool des connexions actives au lieu de la fermer. Une fois que la connexion est retournée au pool, elle est prête à être réutilisée lors du prochain appel Open.

Il n'y a donc aucune raison d'éviter de créer, d'ouvrir ou de fermer des connexions puisqu'en réalité, elles ne sont ni créées, ni ouvertes, ni fermées du tout. C'est "seulement" un indicateur pour le pool de connexions de savoir quand une connexion peut être réutilisée ou non. Mais c'est un indicateur très important, car si une connexion est "en cours d'utilisation" (supposée par le pool de connexions), une nouvelle connexion physique doit être ouverte vers le SGBD, ce qui est très coûteux.

Vous n'obtenez donc aucune amélioration des performances, bien au contraire. Si la taille maximale du pool spécifiée (100 est la valeur par défaut) est atteinte, vous pourriez même obtenir des exceptions (trop de connexions ouvertes ...). Cela n'impactera pas seulement les performances de manière importante, mais sera aussi une source d'erreurs désagréables et (sans utiliser de transactions) d'une zone de vidage des données.

Si vous utilisez même des connexions statiques, vous créez un verrou pour chaque thread essayant d'accéder à cet objet. ASP.NET est un environnement multi-threading par nature. Il y a donc de fortes chances pour que ces verrous causent des problèmes de performances au mieux. En fait, tôt ou tard, vous obtiendrez de nombreuses exceptions différentes (comme votre ExecuteReader nécessite une connexion ouverte et disponible).

Conclusion:

  • Ne réutilisez pas les connexions ni aucun objet ADO.NET du tout.
  • Ne les rendez pas statiques/partagés (en VB.NET)
  • Créez toujours, ouvrez (dans le cas des connexions), utilisez, fermez et disposez-les là où vous en avez besoin (par exemple, dans une méthode).
  • Utilisez la instruction using pour disposer et fermer (dans le cas des connexions) de manière implicite

Ceci est valable non seulement pour les connexions (bien que cela soit le plus remarquable). Chaque objet implémentant IDisposable doit être disposé (le plus simplement par une instruction using), d'autant plus dans l'espace de noms System.Data.SqlClient.

Tout ce qui précède plaide contre une classe DB personnalisée qui encapsule et réutilise tous les objets. C'est la raison pour laquelle j'ai commenté pour la jeter à la poubelle. C'est seulement une source de problèmes.


Éditer : Voici une implémentation possible de votre méthode retrievePromotion :

public Promotion retrievePromotion(int promotionID)
{
    Promotion promo = null;
    var connectionString = System.Configuration.ConfigurationManager.ConnectionStrings["MainConnStr"].ConnectionString;
    using (SqlConnection connection = new SqlConnection(connectionString))
    {
        var queryString = "SELECT PromotionID, PromotionTitle, PromotionURL FROM Promotion WHERE PromotionID=@PromotionID";
        using (var da = new SqlDataAdapter(queryString, connection))
        {
            // vous pourriez également utiliser un SqlDataReader à la place
            // notez qu'un DataTable n'a pas besoin d'être disposé car il n'implémente pas IDisposable
            var tblPromotion = new DataTable();
            // évitez les injections SQL
            da.SelectCommand.Parameters.Add("@PromotionID", SqlDbType.Int);
            da.SelectCommand.Parameters["@PromotionID"].Value = promotionID;
            try
            {
                connection.Open(); // pas nécessairement nécessaire dans ce cas car DataAdapter.Fill le fait autrement
                da.Fill(tblPromotion);
                if (tblPromotion.Rows.Count != 0)
                {
                    var promoRow = tblPromotion.Rows[0];
                    promo = new Promotion()
                    {
                        promotionID    = promotionID,
                        promotionTitle = promoRow.Field("PromotionTitle"),
                        promotionUrl   = promoRow.Field("PromotionURL")
                    };
                }
            }
            catch (Exception ex)
            {
                // journalisez cette exception ou remontez-la dans la pile d'appels
                // nous n'avons pas besoin d'un bloc finally pour fermer la connexion car elle sera fermée implicitement dans une instruction using
                throw;
            }
        }
    }
    return promo;
}

1 votes

C'est vraiment utile pour donner un paradigme de travail de connexion. Merci pour cette explication.

0 votes

Bien écrit, une explication pour quelque chose que beaucoup de gens découvrent accidentellement, et j'espère que plus de gens le savent. (+1)

1 votes

Merci monsieur, je pense que c'est la meilleure explication sur ce sujet que j'aie jamais lue, un sujet très important sur lequel de nombreux débutants se trompent. Je tiens à vous féliciter pour votre excellente capacité d'écriture.

1voto

Oraculum Points 1409

J'ai attrapé cette erreur il y a quelques jours.

Dans mon cas, c'était parce que j'utilisais une Transaction sur un Singleton.

.Net ne fonctionne pas bien avec Singleton comme indiqué ci-dessus.

Ma solution était la suivante:

public class DbHelper : DbHelperCore
{
    public DbHelper()
    {
        Connection = null;
        Transaction = null;
    }

    public static DbHelper instance
    {
        get
        {
            if (HttpContext.Current is null)
                return new DbHelper();
            else if (HttpContext.Current.Items["dbh"] == null)
                HttpContext.Current.Items["dbh"] = new DbHelper();

            return (DbHelper)HttpContext.Current.Items["dbh"];
        }
    }

    public override void BeginTransaction()
    {
        Connection = new SqlConnection(Entity.Connection.getCon);
        if (Connection.State == System.Data.ConnectionState.Closed)
            Connection.Open();
        Transaction = Connection.BeginTransaction();
    }
}

J'ai utilisé HttpContext.Current.Items pour mon instance. Cette classe DbHelper et DbHelperCore est ma propre classe

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X