ExecuteReader richiede una connessione aperta e disponibile. Lo stato corrente della connessione è Connessione


114

Quando si tenta di connettersi al database MSSQL tramite ASP.NET online, si otterrà quanto segue quando due o più persone si connettono contemporaneamente:

ExecuteReader richiede una connessione aperta e disponibile. Lo stato corrente della connessione è Connessione.

Il sito funziona bene sul mio server localhost.

Questo è il codice approssimativo.

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

Posso sapere cosa potrebbe essere andato storto e come risolverlo?

Modifica: non dimenticare, la mia stringa di connessione e la connessione sono entrambe statiche. Credo che questo sia il motivo. Si prega di avvisare.

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

24
Non utilizzare connessioni condivise / statiche in un ambiente multithreading come ASP.NET poiché stai generando blocchi o eccezioni (troppe connessioni aperte ecc.). Getta la tua classe DB nel bidone della spazzatura e crea, apri, usa, chiudi, elimina gli oggetti ado.net dove ti servono. Dai un'occhiata anche alla dichiarazione di utilizzo.
Tim Schmelter

2
potete darmi dettagli su SqlOpenConnection () e sql.ExecuteReader (); funzioni? ..
ankit rajput

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

@ GuoHongLim: ho dimenticato di menzionare che anche uno statico conStringnon aggiunge nulla in termini di prestazioni poiché è comunque memorizzato nella cache per impostazione predefinita (come ogni valore di configurazione per l'applicazione corrente).
Tim Schmelter

... e solo per renderlo un noto-sconosciuto: garantire che anche la gestione delle transazioni / unità di lavoro del database sia corretta è lasciato come esercizio per il lettore.
mwardm

Risposte:


226

Scusate solo per aver commentato in primo luogo, ma sto postando quasi ogni giorno un commento simile poiché molte persone pensano che sarebbe intelligente incapsulare la funzionalità ADO.NET in una classe DB (anch'io 10 anni fa). Per lo più decidono di utilizzare oggetti statici / condivisi poiché sembra essere più veloce che creare un nuovo oggetto per qualsiasi azione.

Questa non è né una buona idea in termini di prestazioni né in termini di sicurezza contro i guasti.

Non braccare sul territorio del Connection-Pool

C'è una buona ragione per cui ADO.NET gestisce internamente le connessioni sottostanti al DBMS nel pool di connessioni ADO-NET :

In pratica, la maggior parte delle applicazioni utilizza solo una o poche configurazioni diverse per le connessioni. Ciò significa che durante l'esecuzione dell'applicazione, molte connessioni identiche verranno aperte e chiuse ripetutamente. Per ridurre al minimo il costo di apertura delle connessioni, ADO.NET utilizza una tecnica di ottimizzazione denominata pool di connessioni.

Il pool di connessioni riduce il numero di volte in cui è necessario aprire nuove connessioni. Il pooler mantiene la proprietà della connessione fisica. Gestisce le connessioni mantenendo attivo un insieme di connessioni attive per ciascuna configurazione di connessione data. Ogni volta che un utente chiama Apri su una connessione, il pooler cerca una connessione disponibile nel pool. Se è disponibile una connessione in pool, la restituisce al chiamante invece di aprire una nuova connessione. Quando l'applicazione chiama Close sulla connessione, il pooler la restituisce alla serie di connessioni attive in pool invece di chiuderla. Una volta che la connessione viene restituita al pool, è pronta per essere riutilizzata alla successiva chiamata Open.

Quindi ovviamente non c'è motivo di evitare di creare, aprire o chiudere connessioni poiché in realtà non vengono create, aperte e chiuse affatto. Questo è "solo" un flag per il pool di connessioni per sapere quando una connessione può essere riutilizzata o meno. Ma è un flag molto importante, perché se una connessione è "in uso" (il pool di connessioni presume), una nuova connessione fisica deve essere aperta al DBMS che è molto costosa.

Quindi non stai ottenendo alcun miglioramento delle prestazioni ma il contrario. Se viene raggiunta la dimensione massima del pool specificata (100 è l'impostazione predefinita), si otterrebbero anche delle eccezioni (troppe connessioni aperte ...). Quindi questo non solo avrà un impatto enorme sulle prestazioni, ma sarà anche una fonte di errori fastidiosi e (senza utilizzare Transazioni) un'area di dumping dei dati.

Se stai anche utilizzando connessioni statiche, stai creando un blocco per ogni thread che tenta di accedere a questo oggetto. ASP.NET è un ambiente multithreading per natura. Quindi c'è una grande possibilità per questi blocchi che causano al meglio problemi di prestazioni. In realtà prima o poi avrai molte eccezioni diverse (come il tuo ExecuteReader richiede una connessione aperta e disponibile ).

Conclusione :

  • Non riutilizzare affatto le connessioni o qualsiasi oggetto ADO.NET.
  • Non renderli statici / condivisi (in VB.NET)
  • Crea, apri sempre (in caso di Connessioni), usa, chiudi e smaltiscili dove ti servono (ad esempio in un metodo)
  • utilizzare il using-statementper smaltire e chiudere (in caso di connessioni) implicitamente

Questo è vero non solo per le connessioni (anche se più evidente). Ogni oggetto implementato IDisposabledovrebbe essere disposto (più semplice da using-statement), tanto più nel System.Data.SqlClientnamespace.

Tutto quanto sopra parla contro una DB-Class personalizzata che incapsula e riutilizza tutti gli oggetti. Questo è il motivo per cui ho commentato di cestinarlo. Questa è solo una fonte di problemi.


Modifica : ecco una possibile implementazione del tuo retrievePromotionmetodo:

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

questo è davvero utile per dare un paradigma di lavoro di connessione. Grazie per questa spiegazione.
uninvincent

ben scritta, una spiegazione per qualcosa che molte persone scoprono accidentalmente, e vorrei che più persone lo sapessero. (+1)
Andrew Hill

1
Grazie signore, penso che questa sia la migliore spiegazione su questo argomento che io abbia mai letto, un argomento che è molto importante e che molti neofiti sbagliano. Devo complimentarmi per la tua eccellente capacità di scrittura.
Sasinosoft

@Tim Schmelter come posso fare in modo che le mie query in esecuzione su thread diversi utilizzino una singola transazione per eseguire il commit / rollback utilizzando l'approccio suggerito?
geeko

1

Ho riscontrato questo errore alcuni giorni fa.

Nel mio caso era perché stavo usando una transazione su un Singleton.

.Net non funziona bene con Singleton come indicato sopra.

La mia soluzione era questa:

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

Ho usato HttpContext.Current.Items per la mia istanza. Questa classe DbHelper e DbHelperCore è la mia classe

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.