Implementare IDISPOSABLE in una classe per connessione a database

lunedì 26 marzo 2012 - 18.36
Tag Elenco Tags  VB.NET  |  .NET 4.0  |  Windows XP  |  Visual Studio Express  |  MySQL 5.0  |  Firefox

ravalon Profilo | Expert

SAlve, sono qui a chiedervi lumi sull'uso dell'implementazione IDISPOSABLE per una classe che uso per connettermi a database MySQL.

Ho il problema che mi rimangono un sacco di connessioni in stato di SLEEP e cosi ho pensato che l'errore potesse essere nel mancato uso di questa disposizione sulla classe...

La mia classe (per quello che interessa questo topic) è cosi costruita

Il codice sorgente non è stato renderizzato qui
perchè non c'è sufficiente spazio.
Clicca qui per visualizzarlo in una nuova finestra


come vedete dichiaro degli oggetti ODBC.odbcconnection ma non ho la minima idea di come mai non si chiudano correttamente tramite la sub ChiudiConnessione alla quale passo il tipo di connessione poco prima utilizzato...

La ChiudiConnessione la chiamo dopo ogni utilizzo della connessione e per sicurezza anche nella UNLOAD della masterpage cosi da verificare se le ho chiuse, ma purtroppo non funziona...

Ho provato ad implementare IDISPOSABLE ma nel mio caso non so come utilizzarla....

come e dove devo chiamare la chiusura degli oggetti per far si di non avere decine di connessioni in SLEEP ??

grazie

Gluck74 Profilo | Guru

guardandola così su due piedi (anche se non ho più molta dimestichezza con VB), posso azzardare a dire che questa classe è abbastanza "pericolosa".

Mi sembra che serva solo per aprire e chiudere connessioni, che poi sono lasciate pubbliche all'esterno tramite proprietà. Presumo che quindi sarà il codice "utilizzatore" ad usare la connessione per gli scopi prefissati.

Mi permetto di suggerirti un'altra implementazione:

Rendi la connessione PRIVATA, o addrittura toglia!!!! (sarà dichiarata ed usata al momento giusto)
Esponi solo metodi che restituiscono dati, esempio (ti scrivo in C# perché il VB non me lo ricordo):
public DataTable ExecuteSelect(string SQL, params IDataParameter[] procParam) { ... }; public IDataReader ExecuteReader(string SQL, params IDataParameter[] procParam) { ... }; public DataTable ExecuteSP(string SP_Name, params IDataParameter[] procParam) { ... }; public object ExecuteScalar(string sql, params IDataParameter[] procParam) { ... };

e tutte le possibili forme in overloading dei metodi che potrebbero servirti.
Sarà la classe che internamente instanzia la connessione, esegue l'operazione richiesta (reader, datatable, ecc ecc), e subito chiude la connessione.
Ad esempio internamente ogni metodo ipotizzato sopra, potrebbe chiemare una procedura tipo questa:
using(SqlConnection conn = new SqlConnection(connstring)) { ... ... }
ho usato SqlConnection ma puoi usare la classe che ritieni più giusta.
Nota l'utilizzo di "using", che sfrutta appunto l'interfaccia IDisposable implementata dalla connessione.
In questo modo non dovrà essere la tua classe ad essere disposable.

In questo modo l'utilizzatore non dovrà fare altro che richiedere i dati semplicemente passando la query o il nome della SP.

Ciao
fammi sapere

____________
Ricordati di utilizzare il tasto "Accetta" se i nostri consigli ti sono serviti a risolvere il problema.
È il modo per ringraziare chi ti ha aiutato.

ravalon Profilo | Expert

ciao... ho creato una classConnessione cosi fatta perchè proviene da un progetto per WinForm che gira benissimo sperando di adattarlo al web...

La classeconnessione, della quale ho pubblicato veramente una piccola parte, contiene tutti i metodi per le estrazioni dati principalmente utilizate... Dentro questa classe ho creato gli oggetti ODBCConnection e ODBCDataReader per poterli riutilizzare ovunque nel sito web tramite richiamo di un oggetto connessione

Ognio volta che mi devo connettere creo una nuova classeconnessione

Dim conn as new ClassConnessione

richiamo un metodo di estrazione dati che mi è congeniale (molti sono sempre dentro alla classConnessione)

conn.EstraiDati(strsql,objConType,ecc.)

e poi chiudo la connessione...

conn.dispose()

Ora...il problema sta nel fatto che nonostante io chiuda tutte le connessioni dopo l'uso, mi rimangono in SLEEP (quindi non aperte ma chiuse in SLEEP) delle connessioni...

Implementando un codice di DISPOSE nella classe le cose sono molto migliorate (di almeno un 50-60%) perchè evidentemente non distruggevo correttamente gli oggetti ODBCConnection .... però alcune mi rimangono comunque in SLEEP...dopo un po queste si sommano e mi creano un problema di troppe connessioni (inutili peraltro) aperte dall'utente in corso....

E' questo il problema che devo risolvere....

Il codice implementato come dispose è questo....

Il codice sorgente non è stato renderizzato qui
perchè non c'è sufficiente spazio.
Clicca qui per visualizzarlo in una nuova finestra


Tra l'altro...come mai in fase di debug non vedo quante connessioni mi si aprono tramite MySQL Administrator ? Solo se uso IIS vedo lo stato delle connessioni....

Gluck74 Profilo | Guru

vediamo di migliorare il codice provando a fare meno modifiche possibili (solo per semplicità, perché consiglierei di modificare la classe comunque).

P.S.: non ho ancora ben chiaro come usi "ConnessioneDB" e "ChiudiConnessione" però il fatto che passi una connessione come Byval, mi puzza....
le usi dal "client"? ovvero dove istanzi la classe ClassConnessione? questo potrebbe essere l'errore che ti lascia le connessioni appese

Primo passo, usa il costrutto using nel codice che utilizza la tua classe. (non sono sicuro di scrivere correttamente in VB)
using(Dim conn as new ClassConnessione) { conn.EstraiDati(strsql,objConType,ecc.) //presumo sia un DataReader dr = conn.EstraiDati(strsql,objConType,ecc.) }

secondo passo, andiamo a correggere la classe seguendo il principio "ad ognuno le proprie responsabilità"
Non è di certo respondabilità del "client" (intendo il codice che usa ClassConnessione) aprire e chiudere la connessione. Dovrebbero essere responsabilità della stessa classe ClassConnessione. Il client non si deve preoccupare di questo.
Quindi, come detto nel post precendente, sposta la gestione della connessione dentro la classe.

il client quindi dovrebbe avere solo le uniche righe di codice scritte qui sopra.

Prova ad eseguire questi due passi e vediamo se il problema è risolto.

ciao

____________
Ricordati di utilizzare il tasto "Accetta" se i nostri consigli ti sono serviti a risolvere il problema.
È il modo per ringraziare chi ti ha aiutato.

ravalon Profilo | Expert

Ciao e grazie di nuovo...

La ConnessioneDB semplicemente capisce su quale DB deve connettersi (glielo dico io xchè ne uso 2 di db), instanzia la stringa di connessione in base al drivers usato e apre la connessione... nel restante pezzo della classe ci sono metodi di estrazione dati utili ai miei scopi

dunque....la istanzio dal client si, tramite un codice del tipo

dim conn as new classconnessione
try
call conn.estraidati(x,y) 'estraggo i dati e li mostro
...faccio qualcosa
catch
finally ' chiudo l'oggetto inizializzato relativo alla connessione
conn.dispose()
end try

facendo il dispose(), per come ho modificato oggi la classe, nella dispose distruggo tutti gli oggetti....

Quindi si, dal client instanzio la classe e la chiudo nel finally.... non è corretto ??

La ChiudiConnessione ormai dopo queste modifiche la uso giusto un paio di volte per chiudere le risorse ma praticamente non mi serve più a niente perchè si fa tutto nel dispose della classe...

cosi va parecchio ma parecchio meglio...però ho ancora diverse connessioni in SLEEP

Inolter mi dicevi che dovrei forse passare con ByRef ? Spiegami pure ....

Domani quando torno a casa provo le modifiche che mi hai detto...e ti posto anche il pezzo nuovo del dispose cosi mi dici se l'ho usato bene....

ravalon Profilo | Expert

Allora eccoci di nuovo qui....ti metto il codice utilizzato che ha dato dei risultati buoni premettendo che:

---- So che usare USING rilascia in automatico le risorse ma cosi non potrei più servirmi dei costrutti TRY CATCH ed il mio lavoro è tutto inglobato dentro questa gestione degli errori con tanto di tracciamento degli stessi...----

Il processo che seguo ora è questo
1) Inizializzo la classe dal client con
dim conn as new classConnessione
2)entro in un blocco TRY CATCH e richiamo un metodo per estrazione dati con
call conn.EstraiDati(x,y)
e li visualizzo
3) dal client nella FINALLY chiamo la dispose della connessione con
conn.dispose()

Nella dispose() della classe ci sono i richiami per chiudere gli oggetti ODBCConnection, gli unici oggetti che devo chiudere....
...ecco il codice nuovo (posto solo quello che serve a spiegare l'esempio sopracitato e per farmi dire se ho fatto correttamente)

GLI OGGETTI DELLA CLASSE CONNESSIONE
[CODE] Imports Microsoft.VisualBasic Imports System.Data Imports System.Data.Odbc Public Class ClassConnessione Implements IDisposable Public ConnettiGenerale As New OdbcConnection Public ConnettiCatalogo As New OdbcConnection Public Dati As OdbcDataReader Private _connectionError As Boolean = False Private _connectionErrMsg As String = "" Private _dataReaderError As Boolean = False Private _dataReaderErrMsg As String = "" [/CODE]

IL METODO CHE FA LA CONNESSIONE AL DB
Il codice sorgente non è stato renderizzato qui
perchè non c'è sufficiente spazio.
Clicca qui per visualizzarlo in una nuova finestra


UN METODO PER ESTRAZIONE DATI

Il codice sorgente non è stato renderizzato qui
perchè non c'è sufficiente spazio.
Clicca qui per visualizzarlo in una nuova finestra


UNA CHIAMATA DAL CLIENT
Il codice sorgente non è stato renderizzato qui
perchè non c'è sufficiente spazio.
Clicca qui per visualizzarlo in una nuova finestra


LA DISPOSE DELLA CLASSE
Il codice sorgente non è stato renderizzato qui
perchè non c'è sufficiente spazio.
Clicca qui per visualizzarlo in una nuova finestra


Spero di non avere fatto proprio un maialaio completo.... gira e funziona tutto, è solo che mi rimangono appese queste cavolo di connessioni in SLEEP...

Dovrei passare gli oggetti connection per ByRef anzichè ByVal ??

Gluck74 Profilo | Guru

dunque, seguire tutto il codice è un po' difficile in questo modo, ma ho visto un errore generale (ripetuto purtroppo)

Tu esponi al client degli oggetti e dei metodi, e li utilizzi dal client, quando non ne hai assolutamente bisogno.

Perché esponi la connessione per poi ripassarla nel metodo EstraiDati?????
Call clsConn.EstraiDati("SELECT ...", clsConn.ConnettiGenerale)
clsConn.ConnettiGenerale è già l'oggetto connessione interno alla classe!!!!!!! è un rigiro inutile!!!!!!
È già una soluzione migliore (anche se non ottimale), questa:
Call clsConn.EstraiDatiGenerale("SELECT ...") Call clsConn.EstraiDatiCatalogo("SELECT ...")
Idem per il log.
Perché catturi l'errore sia nella classeConn che nel client?
Inoltre, se il metodo scriviLog è della classeConn, lascialo fare a lei, non anche dal client.
Poi, per essere precisi, non è responsabilità n'è del client, nè della classeConn fare il log. Fatti una classe responsabile del log ed usa quella.
E che succede se cambi metodo di log? devi andare a cambiare sia la classeConn che il client?
Non è logico dover cambiare una classe di connessione ad DB se cambi log!! che c'entra il log con il DB?? giusto???
Usala istanziandola dentro la classeConn, o ancora meglio iniettala nel costruttore
Logger myLog = new Logger(... ...); classeConn conn = new classeConn(myLog);
sarà la casse conn, se necessario, a demandare la scrittura del log a myLog.
Cerca su internet "OOP Principles Dependency inverison" o guarda qui:
http://javiernavarromachuca.blogspot.it/2011/08/dependency-inversion-principle.html
http://www.eugenioambrosi.it/2008/02/29/i-principi-del-design-oo/

esegui bene il passo 2 che ti ho spiegato nel post precedente. ci siamo quasi.

ciao

____________
Ricordati di utilizzare il tasto "Accetta" se i nostri consigli ti sono serviti a risolvere il problema.
È il modo per ringraziare chi ti ha aiutato.

ravalon Profilo | Expert

Ciao e grazie di nuovo...

dunque....io passo la conn.connettigenerale ed altre al metodo perchè il metodo è generico ma puo' in quel modo, passando quale oggetto connessione usare, aprire verso uno database o un altro...è per quello che l'ho usato....della serie..."Estrai i dati secondo questa query connettendoti al database XXX usando l'oggetto ODBCConnection YYYY".... forse è una cazzata i metodi prendono questo oggetto passato solo per capire(essendo generici e validi per n database) quale database aprire...

Ho fatto questo per non creare due metodi identici per estrarre lo stesso tipo di dati da due database distinti, altrimenti come tu dici avrei dovuto fare un metodo per EstraiDatiGenerici e uno per EstraiDatiCatalogo e via dicendo....ognuno identico all'altro se non che per il database aperto....non so se mi sono spiegato.... in alcuni metodi anzichè passare l'oggetto ODBCConnection passo una stringa che allo stesso modo dice quale database aprire.... in termini di risorse il primo metodo è sbagliato quindi ? consumo più risorse ??
...ne caso fosse cosi, se passo a tutti una stringa anzichè l'oggetto ODBCConnection che come tu dici è un giro inutile la cosa migliora ??

Gluck74 Profilo | Guru

ciao,

si, ti confermo che sarebbe meglio non esporre la connessione all'esterno solo per passarla come parametro al metodo che fa la select.
Piuttosto passa una stringa come fai già o un enumeratore che ti crei tu.

ma anche creare due metodi diversi è giusto, sono loro stessi all'interno che scelgono la connessione.
private effettuarichiesta(sql, connessione) { //il codice attuale che richiede i dati } public EstraiDatiGenerali(string sql) { effettuarichiesta(sql, connGenerale) //connGenerale in questo modo rimane privata } public EstraiDatiDB_X(string sql) { effettuarichiesta(sql, connX) } public EstraiDatiDB_Y(string sql) { effettuarichiesta(sql, connY) }
come vedi il codice non lo duplichi, lo metti in una funzione privata comune

____________
Ricordati di utilizzare il tasto "Accetta" se i nostri consigli ti sono serviti a risolvere il problema.
È il modo per ringraziare chi ti ha aiutato.

ravalon Profilo | Expert

scusami perchè queste due non sono duplicate quasi in tutto tranne che per l'oggetto odbcconnection utilizzato ?

public EstraiDatiDB_X(string sql)
{
effettuarichiesta(sql, connX)
}
public EstraiDatiDB_Y(string sql)
{
effettuarichiesta(sql, connY)
}

Ho centinaia di funzioni e sub in questo sito, immagina questa situazione con codici molto più lunghi moltiplicata per queste centinaia...inoltre la manutenzione del codice sarebbe più onerosa con tante funzioni quasi uguali da modificare...

....però farò come tu mi hai detto, passero una stringa per far capire quale DB utilizzare..... non credevo che passando alla sub un oggetto ODBCConnection consumassi risorse dato che comunque la classe Connessione la avevo inizializzata comunque sia per richiamare i metodi di estrazione dati....

Gluck74 Profilo | Guru

no, non è proprio che consumi risorse, è che magari passando avanti e indietro l'oggetto connessione, forse rimane un riferimento da qualche parte e quindi la connessione non viene chiusa.

facciamo così, se riesci a fare un pacchetto con i file, li metto in VS e vedo di capire meglio il codice.

ciao

____________
Ricordati di utilizzare il tasto "Accetta" se i nostri consigli ti sono serviti a risolvere il problema.
È il modo per ringraziare chi ti ha aiutato.

ravalon Profilo | Expert

ok ora capisco meglio....proverò a maggior ragione a passare una stringa per queste funzioni (per molte lo faccio già) ...magari provo su alcune pagine dove rimangono più connessioni appese...se migliora estendo la modifica a tutto il sito, se non cambia niente vuol dire che non è quello...

....fare un pacchetto è un po difficile per via delle tante routine che si intersecano però ti ringrazio della proposta.

Provo queste mod e ti faccio sapere.... grazie comunque per la tua disponibilità

Gluck74 Profilo | Guru

figurati, è un piacere.

ma per caso sei lo Stefano Ravagni del sito pcprimipassi.it?


____________
Ricordati di utilizzare il tasto "Accetta" se i nostri consigli ti sono serviti a risolvere il problema.
È il modo per ringraziare chi ti ha aiutato.

ravalon Profilo | Expert

si sono io perchè ?

Ho effettuato quelle modifiche in cui toglievo il passaggio dell'oggetto ODBCConnection ai vari metodi, ora passo una variabile stringa che alleggerisce sicuramente il tutto ma non è cambiato niente in quanto al problema iniziale, rimangono delle connessioni in stato di sleep.

Avevo creato una funzione che fai il kill dei processi in stato di sleep ogni tot secondi ma quelli del server non mi permettono di avere i privilegi necessari per poter vedere i processi e killarli nonostante il database sia mio....assurdo secondo me perchè avrei risolto il problema ma evidentemente bisognerà che lo elimini piuttosto che trovare la classica pezza....

Ho letto in giro che il driver odbc 3.51 per MySQL da anche questi problemi ma non so più come correggerlo...cosa mi consigli adesso ?

ravalon Profilo | Expert

Ciao....allora ho trovato la soluzione ai miei problemi !!!

Ho apportato quelle modifiche del passaggio di stringa a favore del passaggio di un oggetto odbc connection ma non cambiava niente, ancora tante connessioni in sleep mode....

Dato che sul server in uso non mi davano il permesso di usare un funzione che faceva il kill dei processi in sleep mode, ho fatto una cosa drastica anche sotto la spinta di quello che si legge in rete, ossia che ODBC ha problemi con .NET (non ho capito se il drivers di microsoft o quello specifico)....figuriamoci poi oggetti come ODBC 3.51 che si usavano 10 anni fa...

Allora ho installato il MyQSL Connector/NET 6.3.8, ho messo un riferimento alla libreria e ho cambiato tutti gli oggetti per connessioni, datareader e oggetti command...

Tutto testato, sia in locale che sul server e di connessioni in sleep nemmeno l'ombra...

Direi quindi che il problema era il driver ODBC per NET...

Per far funzionare il tutto ho dovuto copiare a mano la libreria mysql.data.dll nella BIN del sito....non sono riuscito a farcela andare da solo, non so perchè..... ma come funziona in questo caso ASP.NET, cerca i riferimenti che non trova nella GAC del server all'interno della BIN del sito ???

Gluck74 Profilo | Guru

>Ciao....allora ho trovato la soluzione ai miei problemi !!!
>
Benissimo

>Allora ho installato il MyQSL Connector/NET 6.3.8, ho messo un
>riferimento alla libreria e ho cambiato tutti gli oggetti per
>connessioni, datareader e oggetti command...
>Tutto testato, sia in locale che sul server e di connessioni
>in sleep nemmeno l'ombra...
>
>Direi quindi che il problema era il driver ODBC per NET...
Buono a sapersi..... ;-)


>Per far funzionare il tutto ho dovuto copiare a mano la libreria
>mysql.data.dll nella BIN del sito....non sono riuscito a farcela
>andare da solo, non so perchè..... ma come funziona in questo
>caso ASP.NET, cerca i riferimenti che non trova nella GAC del
>server all'interno della BIN del sito ???
Esatto.
I riferimenti di un progetto li puoi vedere con tasto destro sul progetto -> Proprietà -> Riferimenti.
Vedrai il nome della libreria che viene usata nel progetto, e come seconda colonna il tipo, che ti indica appunto se è una libreria nella GAC, nella BIN, o "progetto".

____________
Ricordati di utilizzare il tasto "Accetta" se i nostri consigli ti sono serviti a risolvere il problema.
È il modo per ringraziare chi ti ha aiutato.

ravalon Profilo | Expert

Si lo sapevo però a me indica che è nella GAC, quindi l'ho dovuta copiare a mano nella BIN...c'è un modo per fargliela prendere da se ? (anche se comunque mi funziona senza problemi)...

....ah...come mai mi hai chiesto chi ero, ci conosciamo ?

Gluck74 Profilo | Guru

in teoria se la dll è nalla GAC, non hai la necessità di copiarla nella BIN.

P.S.: no, non ci conosciamo. L'altro giorno stavo semplicemente girovagando su internet e sono finito sul tuo sito. Ho visto il nome e.... 2+2.. ;-)


____________
Ricordati di utilizzare il tasto "Accetta" se i nostri consigli ti sono serviti a risolvere il problema.
È il modo per ringraziare chi ti ha aiutato.

ravalon Profilo | Expert

ok ho capito... beh in effetti ho pensato anche io che se la copiasse da solo nella bin ma cosi non è stato....l'ho fatto a mano ed il gioco è risolto....grazie per la pazienza e la disponibilità !!!
Partecipa anche tu! Registrati!
Hai bisogno di aiuto ?
Perchè non ti registri subito?

Dopo esserti registrato potrai chiedere
aiuto sul nostro Forum oppure aiutare gli altri

Consulta le Stanze disponibili.

Registrati ora !
Copyright © dotNetHell.it 2002-2017
Running on Windows Server 2008 R2 Standard, SQL Server 2012 & ASP.NET 3.5