domenica 17 giugno 2007

Array Delegate e Metodi Anonimi - Esportare un DataReader in un file di testo

Per esportare un DataReader su un file di testo il framework ci mette parecchi metodi a disposizione.
L'apertura del database è quella "classica" 

C# 

SqlConnection cn = new SqlConnection(Settings.Default.Connessione);
SqlCommand cm
= new SqlCommand("SELECT * FROM customers", cn);


VB.NET



Dim cn As New SqlConnection(My.Settings.Connessione)
Dim cm As New SqlCommand(
"SELECT * FROM customers", cn)


Il successivo ExecuteReader dato sul command genera l'oggetto DataReader.


Le righe poi vengono lette tramite un ciclo sul datareader:


while (dr.Read())

All'interno del ciclo ogni oggetto "dr" è un array di object ciascuno contenente i dati della tabella correlata.
La scrittura in un file dell'array è a questo punto solamente una questione di preferenze e conoscenze.


Il metodo più classico è scorrere l'array con un ciclo:
C#



for (int k = 0; k < dr.FieldCount; k++)


VB.NET

For k As Integer = 0 To dr.FieldCount - 1


E successivamente scrivere il file di testo con i metodi Write o WriteLine dello StreamWriter.


Alternativa - Conversione del datareader in un array e scrittura dell'arrray sullo stream
Si potrebbe pensare che l'opzione "scrittura array" sia folle quando si ha già a disposizione tutta la riga del datareader già pronta per essere inviata al file.

Mi solleticava questa opzione e l'ho esplorata il trucco è partire da un array di oggetti e usare il metodo GetValues del DataReader.
Purtroppo non è possibile convertire direttamente un DataReader in un array di stringhe perchè l'unico parametro accettato è un array di object.
E' ovvio che per poter utilizzare l'opzione string.Join che concatena un array e restituisce la riga già pronta per l'invio allo stream è necessario convertire ulteriormente l'array di object in array di stringhe.

C# 



object[] objRiga = new object[dr.FieldCount];
int i = dr.GetValues(objRiga);
string[] datiRiga = new string[dr.FieldCount];


VB.NET



Dim objRiga As Object() = New Object(dr.FieldCount - 1) {}
Dim i As Integer = dr.GetValues(objRiga)
Dim datiRiga As String() = New String(dr.FieldCount - 1) {}


Nota: la gestione degli array è diversa tra C# e VB.NET infatti quando si inserisce il "quanti" su VB.NET si deve tener conto che tale numero è l'Ubound dell'array, non dice la dimensione ma l'ultimo elemento raggiungibile, partendo da 0 è ovvio che Dim c() As New Object(10) genera 11 elementi


A questo punto, nella mia ricerca sono saltate fuori due possibilità diverse



  1. Convertire l'array in stringa utilizzando Array.Copy

  2. Convertire l'array in stringa richiamando un metodo anonimo da passare come delegate alla funzione Array.ConvertAll<>(), ovviamente si tratta di utilizzare i Generics

Come si potrà vedere entrambi i metodi per la mia applicazione, hanno dei problemi 


Array.Copy richiede che il tipo di provenienza sia uguale al tipo di destinazione, in un datarow è ovvio che non ci sono solo stringhe, quindi ho commentato Array.Copy e mi sono fatto la mia funzione privata di conversione in stringa.


Ad Array.ConvertAll invece non piacciono i null


C#



// scelto il metodo di copiare l'array
string[] MetodoArray(object[] objRiga)
{
string[] datiRiga = new string[objRiga.Length];
// questo metodo non funziona perchè ci sono campi con tipi diversi
// Array.Copy(objRiga, 0,datiRiga,0,datiRiga.Length);
// ritorniamo al metodo classico
for (int i = 0; i < objRiga.Length; i++)
{
datiRiga[i]
= objRiga[i] as string;
}
return datiRiga;
}
// il più elegante, utilizzare i delegate
string[] MetodoDelegate(object[] objRiga)
{
// questo metodo non ammette null su objRiga
// sono pertanto costretto a togliere i null dalla piazza
for (int i = 0; i < objRiga.Length; i++)
{
if (objRiga[i].Equals(DBNull.Value))
objRiga[i]
= String.Empty;
}
return Array.ConvertAll<object, string>(objRiga, delegate(object obj) { return (string)obj; });
}


VB.NET


 


' scelto il metodo di copiare l'array
Private Function MetodoArray(ByVal objRiga As Object()) As String()
Dim datiRiga As String() = New String(objRiga.Length - 1) {}
' questo metodo non funziona perchè ci sono campi con tipi diversi
' Array.Copy(objRiga, 0,datiRiga,0,datiRiga.Length);
' ritorniamo al metodo classico
For i As Integer = 0 To objRiga.Length - 1
datiRiga(i)
= TryCast(objRiga(i), String)
Next
Return datiRiga
End Function
' il più elegante, utilizzare i delegate
Private Function MetodoDelegate(ByVal objRiga As Object()) As String()
For i As Integer = 0 To objRiga.Length - 1
' questo metodo non ammette null su objRiga
' sono pertanto costretto a togliere i null dalla piazza
If objRiga(i).Equals(DBNull.Value) Then
objRiga(i)
= [String].Empty
End If
Next
Return Array.ConvertAll(Of Object, String)(objRiga, AddressOf ConvertedAnonymousMethod1)
End Function
Private Function ConvertedAnonymousMethod1(ByVal obj As Object) As String
Return DirectCast(obj, String)
End Function

Come si potrà notare non sono riuscito a riprodurre il metodo anonimo su vb.net, l'ho sostituito con un vero metodo il cui indirizzo l'ho passato a ConvertAll.


Entrambi questi metodi mi hanno fatto imparare qualche cosa di nuovo, magari mi capiterà di utilizzarli in programmi "veri".


Riferimenti:


Progetto C#
Progetto VB.NET

Nessun commento: