ADO.NET, DataTable e il metodo Load

Quando si studia .NET uno dei aspetti che più di altri spesso vengono rimarcati è la gestione delle risorse e, in particolare, di tutte le classi che implementano l'interfaccia IDisposable. Come molto di voi sapranno quasi sempre le classi che implementano queste interfacce utilizzano risorse unmanaged ed è per questo che il loro corretto trattamento  è fondamentale al fine di liberare tali risorse. Gli esempi più classici sono gli stream come FileStream e NetworkStream che gestiscono rispettivamente l'accesso al file sistem e alla rete via socket.

Memore di questa buona pratica (sono quasi un fanatico dell'uso del costrutto using), ho spesso strutturato l'accesso al database in maniera tale da renderlo il più rapido ed efficiente possibile e in maniera da liberare al più presto la DbConnection utilizzata.

Tuttavia mi sono reso conto che una delle pratiche che ho spesso utilizzato per caricare i risultati di una query SELECT non è la più efficiente. In particolare, dopo aver correttamente costruito il mio DbCommand, averlo associato alla DbConnection, aver inserito eventuali DbParameter, mia comune pratica a seguito dell'esecuzione del metodo ExecuteReader, era quella di dichiarare una DataTable e di caricare il contenuto del DbDataReader attraverso il metodo Load della classe DataTable.

Come saprete la classe DataTable non è altro che una struttura dati disconnessa che rappresenta in memoria il risultato di una query che restituisce almeno un result set. Di per sé non ha molto senso utilizzarla al giorno d'oggi visto che spesso si usa un ORM per accedere al database, tuttavia esistono casi in cui è necessario affidarsi ancora al buon vecchio ADO.NET.

Il metodo Load della classe DataTable è molto comodo perché carica in memoria il risultato di una query strutturando correttamente la DataTable in maniera che ogni sua DataColumn contenga le informazioni (tipo, nome, ecc....) relative ad ogni colonna del result set.

Ho riscontato tuttavia un problema. Quando si utilizzano i Guid in Oracle, indipendentemente dal provider che utilizzate, che questo sia il Devart, ODP.NET o l'OracleClient del .NET Framework (a proposito quest'ultimo è ormai deprecated nella versione 4.0, quindi non usatelo più) nella vostra DataTable, in corrispondenza della colonna che dovrebbe contenere i Guid vi ritroverete un bel array di byte. Oracle gestisce i Guid come RAW(16), quindi effettivamente un array di 16 byte e, pertanto, il metodo Load della DataTable convertirà la colonna in un bel byte[].

Come risolvere questo problema? Inizialmente avevo pensato di scorrermi successivamente alla Load, nuovamente la DataTable per convertire le DataColumn di byte array in Guid. Inutile dirvi che questa è una pessima pensata perché le prestazioni risultano essere dannatamente penalizzanti, soprattutto in confronto all'esecuzione della medesima operazione su SQL Server dove questo problema non c'è e dove, pertanto, il passaggio di conversione non serve.

La soluzione a questo problema è stata di abbandonare il metodo Load della DataTable e di costruirsela a mano scorrendosi il DbDataReader come un bel ciclo while. Il DbDataReader contiene un metodo di Get per ogni tipo di dato supportato tra i quali il Guid. Poiché quando io carico il risultato dal DbDataReader so perfettamente a priori quale tipo di dato mi aspetto da ogni colonna, è stato sufficiente fare un bel switch case e richiamare il metodo di Get corretto.

Mentre implementavo questa soluzione pensavo di ottenere prestazioni molto basse rispetto all'uso del metodo Load della DataTable e, invece, mi sono dovuto ricredere. Non solo con questo nuovo sistema sono riuscito ad ottenere in ogni colonna i dati del giusto tipo (dovete usare il provider di Devart per poter usare la GetGuid su Oracle in quanto non è supportata dall'OracleClient e dall'ODP.NET), ma le prestazioni complessivamente sono aumentate notevolmente sia su Oracle, che su SQL Server di oltre 5 volte. Se prima per caricare 10.000 record la Load ci impiegava circa 400 ms, con la nuova soluzione ottengo il medesimo risultato in 70-80 ms, notevole non credete?

Certo, la quantità di codice scritta è molto più corposa (passiamo da 1 riga di codice, appunto la Load, ad una ventina di righe), ma poiché l'accesso ai dati è spesso uno dei maggiori colli di bottiglia in un sistema, incrementarne le prestazioni di oltre 5 volte non può che far bene.