2009-09-09 4 views
5

La mia domanda riguarda lo stato di connessione SQL, carico, ecc in base al codice seguente:Ci sono dei problemi nell'utilizzo di un tipo di restituzione IEnumerable <T> per dati SQL?

public IEnumberable<MyType> GetMyTypeObjects() 
{ 
    string cmdTxt = "select * from MyObjectTable"; 

    using(SqlConnection conn = new SqlConnection(connString)) 
    { 
    using(SqlCommand cmd = new SqlCommand(cmdTxt, conn)) 
    { 
     conn.Open(); 
     using(SqlDataReader reader = cmd.ExecuteReader()) 
     { 
     while(reader.Read()) 
     { 
      yield return Mapper.MapTo<MyType>(reader); 
     } 
     } 
    } 
    } 
    yield break; 
} 

posso vedere questo forse essere un problema se ci sono molti processi in esecuzione codice simile con tempi di esecuzione lunghi tra le iterazioni di l'oggetto IEnumerable, poiché le connessioni saranno aperte più a lungo, ecc. Tuttavia, sembra anche plausibile che ciò ridurrà l'utilizzo della CPU sul server SQL perché restituisce solo i dati quando viene utilizzato l'oggetto IEnumerable. Riduce anche l'utilizzo della memoria sul client perché il client deve solo caricare un'istanza di MyType mentre funziona piuttosto che caricare tutte le occorrenze di MyType (eseguendo un iter attraverso l'intero DataReader e restituendo un elenco o qualcosa del genere).

  • Esistono casi si può pensare di dove non si vuole utilizzare IEnumerable in questo modo, o tutte le istanze si pensa che si adatta perfettamente?

  • Che tipo di carico inserisce sul server SQL?

  • È questo qualcosa che si utilizzerà nel proprio codice (salvo eventuali menzioni di NHibernate, Subsonic, ecc.)?

  • -

risposta

2

Non lo userei, perché nasconde ciò che sta accadendo e potrebbe lasciare le connessioni al database senza un corretto smaltimento.

L'oggetto di connessione verrà chiuso dopo aver letto l'ultimo record e se si interrompe la lettura prima che l'oggetto di connessione non venga eliminato. Se, ad esempio, si sa che si hanno sempre dieci record in un risultato e si ha semplicemente un ciclo che legge quei dieci record dall'enumeratore senza effettuare l'undicesima chiamata di lettura che oltrepassa l'ultimo elemento, la connessione non viene chiusa correttamente. Inoltre, se si desidera utilizzare solo una parte del risultato, non è possibile chiudere la connessione senza leggere il resto dei record.

Anche il costruito in estensioni per i enumeratori possono causare questo, anche se si utilizzano correttamente seamlingy:

foreach (MyType item in GetMyTypeObjects().Take(10)) { 
    ... 
} 
+0

Questo è un buon punto, non l'avevo nemmeno considerato. Sono così accecato dal modo in cui lo userei! – scottm

+1

@Guffa: l'utilizzo di metodi di estensione come 'Take' non sarebbe un problema: il metodo' GetMyTypeObjects' è solo zucchero sintattico che crea un oggetto iteratore 'IDisposable'. 'Take' chiamerà il metodo 'Dispose' dell'iteratore una volta terminato, e quindi eliminerà la connessione, il comando, il lettore ecc. – LukeH

+0

@Guffa: E lo stesso vale per qualsiasi altra lettura parziale del set di risultati: finché tu chiama 'Dispose' quando hai finito (o preferibilmente avvolgi tutto in un blocco' using') poi la connessione, il comando, il lettore etc saranno anch'essi eliminati. – LukeH

2

mi raccomando contro pre-ottimizzazione. In molte situazioni, le connessioni verranno raggruppate.

Inoltre non mi aspetto alcuna differenza di carico su SQL Server: la query sarà già stata compilata e sarà in esecuzione.

+0

non sono sicuro cosa intendi per pre-ottimizzazione (non sto cercando di farlo). Stavo pensando che il carico sarebbe stato interessato dal fatto che il server avrebbe mantenuto la posizione del cursore su più connessioni contemporaneamente. – scottm

+0

Pre-ottimizzando, intendo che ti preoccupi dei problemi di prestazioni prima che il tuo codice funzioni. Stai pensando a problemi che potrebbero non esistere. –

+0

Capisco. Inoltre, non voglio codificarmi in un angolo, dovendo ridisegnare i miei metodi di accesso ai dati e i metodi che li useranno in futuro. – scottm

6

Questo non è uno schema che vorrei seguire. Non mi preoccuperei tanto di caricare sul server quanto vorrei sui lucchetti. Seguendo questo schema si integra il processo di recupero dei dati nel flusso della tua logica aziendale, e questa sembra una ricetta completa per i problemi; non hai idea di cosa succede sul lato iterazione e ti stai inserendo in esso. Recupera i tuoi dati in un colpo solo, quindi consenti al codice client di enumerare su di esso una volta chiuso il lettore.

+0

Blocchi sugli oggetti SQL? Non l'ho considerato. Cosa ne pensi di dare un suggerimento nolock (che potrebbe essere praticabile solo in determinate circostanze)? – scottm

+1

Questo eliminerebbe (ovviamente) il problema del blocco, ma continuerai a tenere il lettore aperto per un periodo di tempo indefinito (probabilmente per sempre se il codice che consuma dimentica di eliminare l'enumeratore). Sembra solo un rischio relativamente alto per il basso guadagno. Se c'è un particolare bisogno di questa pratica - suppongo che il consumo di memoria possa qualificarsi - allora non è del tutto negativo, ma sicuramente non mi farei un'abitudine e cercherò alternative. –

+0

E per inciso, non sei affatto garantito "un'istanza di MyType". In effetti, anche se il codice che consuma si occupa solo di un'istanza alla volta, il GC non sta per pulirli immediatamente. Sì, li farai cadere al di fuori del campo di applicazione e sarai idoneo per la raccolta prima, ma non sarà un'impronta di memoria costante. –

1

La mia preoccupazione è che si sta mettersi completamente in balia del codice client.

Hai già detto che il codice chiamante potrebbe mantenere la connessione aperta più a lungo di quanto sia strettamente necessario, ma c'è anche la possibilità che non consentirebbe mai la chiusura/eliminazione degli oggetti.

Finché il codice client utilizza foreach, using ecc o esplicitamente chiama il metodo del enumerator Dispose allora stai bene, ma non c'è nulla per fermarlo facendo qualcosa di simile:

var e = GetMyTypeObjects().GetEnumerator(); 
e.MoveNext(); // open the connection etc 

// forget about the enumerator and go away and do something else 
// now the reader, command and connection won't be closed/disposed 
// until the GC kicks in and calls their finalisers 
Problemi correlati