web-dev-qa-db-fra.com

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

Lors de la tentative de connexion à la base de données MSSQL via ASP.NET en ligne, les messages suivants seront affichés lorsque deux personnes ou plus se connectent simultanément:

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

Le site fonctionne bien sur mon serveur localhost.

C'est le code brut.

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

Puis-je savoir ce qui aurait mal tourné et comment puis-je résoudre le problème?

Edit: Ne pas oublier, ma chaîne de connexion et ma connexion sont statiques. Je crois que c'est la raison. S'il vous plaît donnez votre avis.

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

Désolé de ne faire que des commentaires au départ, mais je publie presque tous les jours un commentaire similaire, car de nombreuses personnes pensent qu'il serait judicieux d'intégrer la fonctionnalité ADO.NET dans une classe de base de données (moi aussi il y a 10 ans). Généralement, ils décident d'utiliser des objets statiques/partagés, car cela semble plus rapide que de créer un nouvel objet pour une action.

Ce n'est ni une bonne idée en termes de performances, ni en termes de sécurité.

Ne pas braconner sur le territoire du Connection-Pool

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

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

Le regroupement de connexions réduit le nombre de fois que de nouvelles connexions doivent être ouvertes. Le pooler conserve la propriété de la connexion physique. Il gère les connexions en maintenant en vie un ensemble de connexions actives pour chaque configuration de connexion donnée. Chaque fois qu'un utilisateur appelle Open sur une connexion, le pooler recherche une connexion disponible dans le pool. Si une connexion 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 pooler la renvoie à l'ensemble mis en pool de connexions actives au lieu de le fermer. Une fois la connexion renvoyée au pool, celui-ci est prêt à être réutilisé lors du prochain appel Open.

Il est donc évident qu’il n’ya aucune raison d’éviter de créer, d’ouvrir ou de fermer des connexions car, en réalité, elles ne sont ni créées, ni ouvertes, ni fermées. Il s’agit "uniquement" d’un indicateur permettant au 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" (le pool de connexions est supposé), une nouvelle connexion physique doit être ouverte au SGBD, ce qui est très coûteux.

Donc, vous n'obtenez aucune amélioration de la performance, mais l'inverse. Si la taille maximale du pool spécifiée (100 par défaut) est atteinte, vous obtiendrez même des exceptions (trop de connexions ouvertes ...). Cela aura donc non seulement un impact considérable sur les performances, mais constituera également une source d'erreurs désagréables et (sans utiliser Transactions) d'une zone de sauvegarde 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 multithreading par nature. Il y a donc de grandes chances que ces verrous causent des problèmes de performances. 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 ou les objets ADO.NET.
  • Ne les rendez pas statiques/partagés (dans VB.NET)
  • Créez toujours, ouvrez (dans le cas de connexions), utilisez, fermez et disposez-les où vous en avez besoin (par exemple dans une méthode)
  • utilisez le using-statement pour vous débarrasser et vous fermer (en cas de connexions) implicitement

C’est vrai non seulement pour les connexions (bien que les plus notables). Chaque objet implémentant IDisposable doit être supprimé (le plus simple avant using-statement ), d’autant plus dans l’espace de noms _System.Data.SqlClient_.

Tout ce qui précède parle contre une classe de base de données personnalisée qui encapsule et réutilise tous les objets. C'est la raison pour laquelle j'ai commenté de le jeter. Ce n'est qu'une source de problème.


Edit : 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))
        {
            // you could also use a SqlDataReader instead
            // note that a DataTable does not need to be disposed since it does not implement IDisposable
            var tblPromotion = new DataTable();
            // avoid SQL-Injection
            da.SelectCommand.Parameters.Add("@PromotionID", SqlDbType.Int);
            da.SelectCommand.Parameters["@PromotionID"].Value = promotionID;
            try
            {
                connection.Open(); // not necessarily needed in this case because DataAdapter.Fill does it otherwise 
                da.Fill(tblPromotion);
                if (tblPromotion.Rows.Count != 0)
                {
                    var promoRow = tblPromotion.Rows[0];
                    promo = new Promotion()
                    {
                        promotionID    = promotionID,
                        promotionTitle = promoRow.Field<String>("PromotionTitle"),
                        promotionUrl   = promoRow.Field<String>("PromotionURL")
                    };
                }
            }
            catch (Exception ex)
            {
                // log this exception or throw it up the StackTrace
                // we do not need a finally-block to close the connection since it will be closed implicitely in an using-statement
                throw;
            }
        }
    }
    return promo;
}
_
215
Tim Schmelter