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

114

Lors de la tentative de connexion à la base de données MSSQL via ASP.NET en ligne, j'obtiendrai ce qui suit lorsque deux ou plusieurs personnes 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.

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

Puis-je savoir ce qui a pu mal tourner et comment puis-je y remédier?

Edit: Ne pas oublier, ma chaîne de connexion et ma connexion sont toutes deux en statique. 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;
Guo Hong Lim
la source
24
N'utilisez pas de connexions partagées / statiques dans un environnement multithreading comme ASP.NET car vous générez des verrous ou des exceptions (trop de connexions ouvertes, etc.). Jetez votre DB-Class à la poubelle et créez, ouvrez, utilisez, fermez, supprimez les objets ado.net là où vous en avez besoin. Jetez également un œil à l'instruction using.
Tim Schmelter
2
pouvez-vous me donner des détails sur SqlOpenConnection (); et sql.ExecuteReader (); fonctions? ..
ankit rajput
private void SqlOpenConnection () {try {conn = new SqlConnection (); conn.ConnectionString = conString; conn.Open (); } catch (SqlException ex) {throw ex; }}
Guo Hong Lim
@GuoHongLim: j'ai oublié de mentionner que même un statique conStringn'ajoute rien en termes de performances car il est de toute façon mis en cache par défaut (comme chaque valeur de configuration pour l'application actuelle).
Tim Schmelter
... et juste pour en faire une inconnue connue: s'assurer que vous avez également la bonne gestion des transactions / unités de travail de votre base de données est un exercice pour le lecteur.
mwardm

Réponses:

227

Désolé de ne commenter qu'en premier lieu, mais je poste presque tous les jours un commentaire similaire car beaucoup de gens pensent qu'il serait judicieux d'encapsuler la fonctionnalité ADO.NET dans une DB-Class (moi aussi il y a 10 ans). La plupart du temps, ils décident d'utiliser des objets statiques / partagés car cela semble être plus rapide que de créer un nouvel objet pour toute 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 pool de connexions ADO-NET :

En pratique, la plupart des applications n'utilisent qu'une ou plusieurs configurations différentes pour les 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 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 gardant 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 groupée est disponible, elle 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 groupé de 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 évidemment aucune raison d'éviter de créer, d'ouvrir ou de fermer des connexions car elles ne sont en fait pas créées, ouvertes et fermées du tout. 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 suppose), une nouvelle connexion physique doit être ouverte au SGBD, ce qui est très coûteux.

Vous n'obtenez donc aucune amélioration des performances, mais le contraire. Si la taille maximale du pool spécifiée (100 est la valeur 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 sera également une source d'erreurs désagréables et (sans utiliser de transactions) une zone de vidage de 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 une grande chance pour ces verrous qui causent au mieux 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 du tout les connexions ou les objets ADO.NET.
  • Ne les rendez pas statiques / partagés (dans VB.NET)
  • Toujours créer, ouvrir (dans le cas de connexions), utiliser, fermer et les disposer là où vous en avez besoin (par exemple dans une méthode)
  • utiliser le using-statementpour supprimer et fermer (en cas de connexions) implicitement

C'est vrai non seulement pour les connexions (bien que le plus notable). Chaque objet implémentantIDisposable doit être supprimé (le plus simple par using-statement), d'autant plus dans l' System.Data.SqlClientespace de noms.

Tout ce qui précède va à l'encontre d'une DB-Class personnalisée qui encapsule et réutilise tous les objets. C'est la raison pour laquelle j'ai commenté pour le détruire. Ce n'est qu'une source de problème.


Edit : Voici une implémentation possible de votre retrievePromotion-method:

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;
}
Tim Schmelter
la source
c'est vraiment utile pour donner un paradigme de travail de connexion. Merci pour cette explication.
aminvincent
bien écrit, une explication pour quelque chose que beaucoup de gens découvrent accidentellement, et j'aimerais que plus de gens le sachent. (+1)
Andrew Hill
1
Merci monsieur, je pense que c'est la meilleure explication sur ce sujet que j'aie jamais lu, un sujet qui est très important et que beaucoup de débutants se trompent. Je dois vous féliciter pour votre excellente capacité d'écriture.
Sasinosoft le
@Tim Schmelter Comment puis-je faire en sorte que mes requêtes s'exécutant sur différents threads utilisent une seule transaction pour la validation / la restauration en utilisant l'approche que vous suggérez?
geeko
1

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

Damon Abdiel
la source