ExecuteReader erfordert eine offene und verfügbare Verbindung. Der aktuelle Status der Verbindung lautet Verbinden

114

Wenn ich versuche, über ASP.NET online eine Verbindung zur MSSQL-Datenbank herzustellen, wird Folgendes angezeigt, wenn zwei oder mehr Personen gleichzeitig eine Verbindung herstellen:

ExecuteReader erfordert eine offene und verfügbare Verbindung. Der aktuelle Status der Verbindung lautet Verbinden.

Die Site funktioniert gut auf meinem localhost-Server.

Dies ist der grobe Code.

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

Darf ich wissen, was möglicherweise schief gelaufen ist und wie ich das Problem beheben kann?

Bearbeiten: Nicht zu vergessen, meine Verbindungszeichenfolge und Verbindung sind beide statisch. Ich glaube das ist der Grund. Bitte beraten.

public static string conString = ConfigurationManager.ConnectionStrings["dbConnection"].ConnectionString;
public static SqlConnection conn = null;
Guo Hong Lim
quelle
24
Verwenden Sie keine gemeinsam genutzten / statischen Verbindungen in einer Multithreading-Umgebung wie ASP.NET, da Sie Sperren oder Ausnahmen generieren (zu viele offene Verbindungen usw.). Werfen Sie Ihre DB-Klasse in den Mülleimer und erstellen, öffnen, verwenden, schließen und entsorgen Sie ado.net-Objekte dort, wo Sie sie benötigen. Schauen Sie sich auch die using-Anweisung an.
Tim Schmelter
2
Können Sie mir Details zu SqlOpenConnection () und sql.ExecuteReader () geben? Funktionen? ..
Ankit Rajput
private void SqlOpenConnection () {try {conn = new SqlConnection (); conn.ConnectionString = conString; conn.Open (); } catch (SqlException ex) {throw ex; }}
Guo Hong Lim
@GuoHongLim: Ich habe vergessen zu erwähnen, dass selbst eine Statik conStringnichts zur Leistung beiträgt, da sie sowieso standardmäßig zwischengespeichert wird (wie jeder Konfigurationswert für die aktuelle Anwendung).
Tim Schmelter
... und nur um es bekannt-unbekannt zu machen: Es bleibt dem Leser als Übung überlassen, sicherzustellen, dass Sie auch Ihre Datenbanktransaktionsbehandlung / Arbeitseinheit korrekt ausführen.
Mwardm

Antworten:

225

Es tut mir leid, dass ich erst einmal kommentiert habe, aber ich poste fast jeden Tag einen ähnlichen Kommentar, da viele Leute denken, dass es klug wäre, die ADO.NET-Funktionalität in eine DB-Klasse zu kapseln (ich auch vor 10 Jahren). Meistens entscheiden sie sich für statische / gemeinsam genutzte Objekte, da dies schneller zu sein scheint als das Erstellen eines neuen Objekts für eine Aktion.

Das ist weder in Bezug auf die Leistung noch in Bezug auf die Ausfallsicherheit eine gute Idee.

Wildern Sie nicht auf dem Territorium des Verbindungspools

Es gibt einen guten Grund, warum ADO.NET die zugrunde liegenden Verbindungen zum DBMS im ADO-NET-Verbindungspool intern verwaltet :

In der Praxis verwenden die meisten Anwendungen nur eine oder mehrere unterschiedliche Konfigurationen für Verbindungen. Dies bedeutet, dass während der Anwendungsausführung viele identische Verbindungen wiederholt geöffnet und geschlossen werden. Um die Kosten für das Öffnen von Verbindungen zu minimieren, verwendet ADO.NET eine Optimierungstechnik, die als Verbindungspooling bezeichnet wird.

Verbindungspooling reduziert die Häufigkeit, mit der neue Verbindungen geöffnet werden müssen. Der Pooler behält das Eigentum an der physischen Verbindung. Es verwaltet Verbindungen, indem es eine Reihe aktiver Verbindungen für jede gegebene Verbindungskonfiguration am Leben erhält. Immer wenn ein Benutzer Open für eine Verbindung aufruft, sucht der Pooler nach einer verfügbaren Verbindung im Pool. Wenn eine gepoolte Verbindung verfügbar ist, gibt sie diese an den Anrufer zurück, anstatt eine neue Verbindung zu öffnen. Wenn die Anwendung Close für die Verbindung aufruft, gibt der Pooler sie an die gepoolten aktiven Verbindungen zurück, anstatt sie zu schließen. Sobald die Verbindung zum Pool wiederhergestellt ist, kann sie beim nächsten offenen Anruf wiederverwendet werden.

Es gibt also offensichtlich keinen Grund, das Erstellen, Öffnen oder Schließen von Verbindungen zu vermeiden, da diese überhaupt nicht erstellt, geöffnet und geschlossen werden. Dies ist "nur" ein Flag, mit dem der Verbindungspool weiß, wann eine Verbindung wiederverwendet werden kann oder nicht. Aber es ist ein sehr wichtiges Flag, denn wenn eine Verbindung "in Gebrauch" ist (der Verbindungspool geht davon aus), muss eine neue physische Verbindung zum DBMS geöffnet werden, was sehr teuer ist.

Sie erzielen also keine Leistungsverbesserung, sondern das Gegenteil. Wenn die angegebene maximale Poolgröße (100 ist die Standardeinstellung) erreicht ist, erhalten Sie sogar Ausnahmen (zu viele offene Verbindungen ...). Dies wirkt sich also nicht nur enorm auf die Leistung aus, sondern ist auch eine Quelle für böse Fehler und (ohne Verwendung von Transaktionen) ein Daten-Dumping-Bereich.

Wenn Sie sogar statische Verbindungen verwenden, erstellen Sie eine Sperre für jeden Thread, der versucht, auf dieses Objekt zuzugreifen. ASP.NET ist von Natur aus eine Multithreading-Umgebung. Es gibt also eine große Chance für diese Sperren, was bestenfalls zu Leistungsproblemen führt. Früher oder später erhalten Sie viele verschiedene Ausnahmen (wie Ihr ExecuteReader eine offene und verfügbare Verbindung benötigt ).

Fazit :

  • Verwenden Sie keine Verbindungen oder ADO.NET-Objekte.
  • Machen Sie sie nicht statisch / freigegeben (in VB.NET)
  • Erstellen, öffnen (bei Verbindungen), verwenden, schließen und entsorgen Sie sie immer dort, wo Sie sie benötigen (z. B. in einer Methode).
  • Verwenden Sie das using-statement, um (bei Verbindungen) implizit zu entsorgen und zu schließen

Dies gilt nicht nur für Verbindungen (obwohl dies am auffälligsten ist). Jedes Objekt, das implementiert wird, IDisposablesollte entsorgt werden (am einfachsten von using-statement), umso mehr im System.Data.SqlClientNamespace.

All dies spricht gegen eine benutzerdefinierte DB-Klasse, die alle Objekte kapselt und wiederverwendet. Das ist der Grund, warum ich kommentiert habe, es zu verwerfen. Das ist nur eine Problemquelle.


Bearbeiten : Hier ist eine mögliche Implementierung Ihrer retrievePromotionMethode:

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
quelle
Dies ist wirklich nützlich, um das Paradigma der Verbindungsarbeit zu vermitteln. Danke für diese Erklärung.
aminvincent
gut geschrieben, eine Erklärung für etwas, das viele Menschen versehentlich entdecken, und ich wünschte, mehr Menschen wüssten das. (+1)
Andrew Hill
1
Vielen Dank, Sir. Ich denke, dies ist die beste Erklärung zu diesem Thema, die ich je gelesen habe. Ein Thema, das sehr wichtig ist und das viele Neulinge falsch machen. Ich muss Ihnen für Ihre hervorragenden Schreibfähigkeiten ein Kompliment machen.
Sasinosoft
@ Tim Schmelter Wie kann ich dafür sorgen, dass meine Abfragen, die auf verschiedenen Threads ausgeführt werden, eine einzige Transaktion zum Festschreiben / Zurücksetzen mit Ihrem vorgeschlagenen Ansatz verwenden?
Geeko
1

Ich habe diesen Fehler vor ein paar Tagen entdeckt.

In meinem Fall lag es daran, dass ich eine Transaktion für einen Singleton verwendet habe.

.Net funktioniert nicht gut mit Singleton, wie oben angegeben.

Meine Lösung war folgende:

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

Ich habe HttpContext.Current.Items für meine Instanz verwendet. Diese Klasse DbHelper und DbHelperCore ist meine eigene Klasse

Damon Abdiel
quelle