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-statement
per smaltire e chiudere (in caso di connessioni) implicitamente
Questo è vero non solo per le connessioni (anche se più evidente). Ogni oggetto implementato IDisposable
dovrebbe essere disposto (più semplice da using-statement
), tanto più nel System.Data.SqlClient
namespace.
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 retrievePromotion
metodo:
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;
}