VB.NET Manipolazione Dati da fonte SQL Server tramite controlli e LINQ (it-IT)
Introduzione
In questo articolo vedremo i passi da compiere per rendere una tabella SQL Server disponibile alla nostra applicazione, utilizzando, oltre al Wizard di Visual Studio per la realizzazione del collegamento all'istanza, le classi DataSet e TableAdapter. Vedremo come i dati residenti su SQL Server possano essere esposti sui controlli, con alcuni esempi, e come possano essere manipolati code-side, per effettuare quindi un aggiornamento della base dati vera e propria. Infine, riporterò alcuni accenni a LINQ, applicato al contesto qui in discussione.
A pochi passi dal DataSet
Sottotitolo forse eccessivamente semplicistico, ma in qualche modo corrispondente al vero: vediamo qui come, utilizzando il DataSource Configuration Wizard, collegarsi alla base dati e creare gli oggetti che ci serviranno sia un'operazione decisamente immediata. Supponiamo di avere un'istanza di SQL Server su cui è presente il database TECHNET. Al suo interno, vi è una tabella di nome People, costituita da un campo Id autoincrementante, e due campi di tipo Varchar, rispettivamente Name e City.
Dal menù di Visual Studio, selezioniamo la voce Project, quindi Add New Data Source
Nella maschera che apparirà, dovremo selezionare la tipologia di oggetto dalla quale leggere i dati di interesse. Nel nostro caso, si tratta di un Database.
Scegliamo ora la tipologia di modello che renderà disponibili i dati letti alla nostra applicazione. La scelta è DataSet.
Verrà quindi presentata una maschera che richiederà i nostri parametri di connessione. Clicchiamo su New Connection, e compiliamo il Wizard secondo i parametri della nostra istanza SQL Server. La stringa di connessione verrà salvata nel file App.config, e potrà essere modificata in caso di migrazione a diversa situazione operativa, magari per cambio istanza, o similari.
Ancora un Next, e verranno mostrati gli oggetti presenti nel database selezionato (nel caso dell'esempio, TECHNET). Procediamo con la selezione della tabella People, e premiamo Next.
Il Wizard completerà le operazioni, chiedendo ancora il nome da assegnare al DataSet creato. Al termine dell'elaborazione, vedremo comparire il nostro DataSet tra i files della soluzione.
Cliccando due volte il file riferito al DataSet, verrà aperto il Designer, attraverso cui possiamo notare come il Wizard abbia creato un oggetto di tipo DataTable, chiamato People, i cui campi sono quelli letti dalla tabella di origine, ed un oggetto TableAdapter, del quale espone i metodi di popolamento, aggiornamento ed eliminazione dei dati, da eseguirsi a mezzo T-SQL, automaticamente generato a partire dallo schema di tabella. È possibile qui effettuare modifiche tra le più disparate: rinomina degli oggetti, delle colonne, variazione rispetto alle proprietà delle stesse, modifica delle query.
Collegamento a DataGridView: esposizione e modifica dei dati
Supponendo di trovarci in ambito Windows Form, supponiamo di aggiungere, ad un ipotetico Form1, una DataGridView. In essa vorremo visualizzare il contenuto della nostra tabella, e poterla variare se vengono effettuate modifiche. Ci interessa che vi sia persistenza quanto ai dati che l'utente si troverà a gestire, ovvero che vi sia un reale aggiornamento dati lato SQL Server.
Il codice a seguire, realizza tali funzionalità. Si inizia col dichiarare due nuove referenze, una al DataSet visto sopra, e la seconda al suo TableAdapter, così come presenti nello schema del DataSet stesso.
Public Class Form1
Dim myDS As New TECHNETDataSet
Dim myTB As New TECHNETDataSetTableAdapters.PeopleTableAdapter
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
myTB.Fill(myDS.People)
DataGridView1.DataSource = myDS.People
End Sub
Private Sub DataGridView1_CellValidated(sender As Object, e As DataGridViewCellEventArgs) Handles DataGridView1.CellValidated
myTB.Update(myDS.People)
End Sub
End Class
All'evento Load del Form, si desidera popolare il DataSet. A questo fine si utilizza il metodo Fill proprio del TableAdapter, passandogli come parametro la DataTable People. La fonte dati così preparata viene impostata come sorgente dati della GridView. Questo è sufficiente a generare, nella GridView, le colonne riferite ai campi di tabella, e mostrare il contenuto di quest'ultima. Se non sono state inibite le proprietà relative all'inserimento, alla cancellazione, o alla modifica dei dati sulla DataGridView, sarà inoltre possibile modificare i dati originari.
Tuttavia, per poterli consolidare, ciò non è sufficiente: dovremo fare in modo che il TableAdapter esegua l'operazione inversa alla precedente, ovvero che - a partire dalla tabella referenziata - possa eseguire un aggiornamento dei dati sottostanti. Ciò si ottiene con il metodo Update. Nello scorcio sopra, la procedura di aggiornamento avviene posteriormente alle operazioni di validazione di una singola cella (ovvero, quando il dato in essa imputato è stato contrassegnato come coerente). Se si desiderasse eseguire tale operazione alla validazione della riga, sarebbe sufficiente utilizzare l'evento RowValidated.
Eseguendo il programma, ed effettuando qualche test relativo ad inserimenti, variazioni, eliminazioni, si noterà come qualsiasi modifica venga automaticamente riportata nella tabella presente nel database di SQL Server.
Collegamento a ComboBox su singola colonna
Alcuni controlli, benchè in possesso di proprietà che permettano il binding di fonti dati, non sono in grado di mostrarle nella loro totalità. Una ComboBox, per esempio, non può mostrare tutte le colonne di una tabella come tali, bensì è possibile visualizzare una sola tra esse. Sui controlli a tendina, possiamo quindi impostare l'origine dati nello stesso modo fatto per la DataGridView, con l'accortezza aggiuntiva di specificare, nella proprietà DisplayMember, quale deve essere il membro di visualizzazione.
Un esempio può essere il seguente:
Public Class Form1
Dim myDS As New TECHNETDataSet
Dim myTB As New TECHNETDataSetTableAdapters.PeopleTableAdapter
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
myTB.Fill(myDS.People)
ComboBox1.DataSource = myDS.People
ComboBox1.DisplayMember = myDS.People.NameColumn.ColumnName
End Sub
End Class
Si esegue cioè la stessa inizializzazione di DataSet e TableAdapter, e si popola il DataSet come in precedenza. Si imposta il DataSource di ComboBox1 come si sarebbe fatto per la DataGridView, e si valorizza la proprietà DisplayMember utilizzando la stringa che definisce il nome di colonna (ColumnName) della colonna che espone il campo Name (NameColumn) sulla tabella People, appartenente al nostro DataSet. In questo modo, eseguendo il programma, noteremo che gli elementi di tendina saranno rappresentati dalla colonna di tabella Name.
Riferimenti a LINQ
Le caratteristiche maggiormente degne di nota delle Language-Integrated Queries sono sicuramente l'univocità del set di istruzioni da usare, indipendente dalla fonte dati di riferimento, e le potenti istruzioni di selezione, che permettono di filtrare in modo conciso ed efficace dati tra i più disparati. Una volta inizializzate correttamente le referenze al DataSet, la sintassi LINQ può essere utilizzata con profitto anche nei casi come quelli di esempio.
Tornando all'esempio della DataGridView, e supponendo di voler estrarre e visualizzare soltanto quei records in cui la colonna Name inizia per "John", potremmo scrivere uno scorcio di questo tipo:
Public Class Form1
Dim myDS As New TECHNETDataSet
Dim myTB As New TECHNETDataSetTableAdapters.PeopleTableAdapter
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
myTB.Fill(myDS.People)
DataGridView1.DataSource = (From pr As TECHNETDataSet.PeopleRow
In myDS.People
Where pr.Name Like "John*").ToList
End Sub
Private Sub DataGridView1_CellValidated(sender As Object, e As DataGridViewCellEventArgs) Handles DataGridView1.CellValidated
myTB.Update(myDS.People)
End Sub
End Class
Si nota che il DataSource della DataGridView non è riferito direttamente ad una DataTable, quanto piuttosto all'estrazione di determinati elementi PeopleRow, estratti in base ad una clausola Where che cerca nella colonna Name la stringa "John*" (dove l'asterisco rappresenta una wildcard). Il fatto che si stia lavorando su una lista filtrata non permette, in questo caso, l'aggiunta di nuovi elementi. Tuttavia, relativamente a quelli che vengono invece mostrati, il richiamo al metodo Update consoliderà correttamente le eventuali modifiche.
Allo stesso modo, riprendendo il caso della ComboBox, ed ipotizzando di voler riprodurre il caso in cui esso verrà popolato dal contenuto della colonna Name, un esempio potrebbe essere:
Public Class Form1
Dim myDS As New TECHNETDataSet
Dim myTB As New TECHNETDataSetTableAdapters.PeopleTableAdapter
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
myTB.Fill(myDS.People)
ComboBox1.DataSource = (From pr As TECHNETDataSet.PeopleRow
In myDS.People
Select pr.Name).ToList
End Sub
End Class
In questo caso, dal momento che sappiamo a priori di dover collegare una lista di stringhe, potremmo bypassare il discorso relativo al DisplayMember visto sopra semplicemente aggiungendo un'ulteriore selezione nella nostra query. Nell'esempio appena riportato, vengono estratte tutte le righe della tabella People, e da esse viene ancora estratto il solo elemento Name. Naturalmente, per continuare ad utilizzare DisplayMember sarebbe sufficiente scrivere:
ComboBox1.DataSource = (From pr As TECHNETDataSet.PeopleRow
In myDS.People).ToList
ComboBox1.DisplayMember = "Name"
Ovvero, evitando di estrarre sottoinsiemi della prima selezione, e demandando alla proprietà DisplayMember il compito di visualizzare una specifica colonna.
Modifica Dati tramite LINQ
Supponiamo di voler eseguire un aggiornamento dati che non passi per diretta interazione dell'utente. Pensiamo ad un campo di database che, al verificarsi di determinate condizioni, debba subire una variazione. Nel caso in esempio, vogliamo che le città specificate su ogni record siano modificate in "Turin". Tramite LINQ, unitamente ai metodi di aggiornamento dati visti fin qui, è possibile farlo senza occuparsi dello strato relativo alla connessione col database.
Public Class Form1
Dim myDS As New TECHNETDataSet
Dim myTB As New TECHNETDataSetTableAdapters.PeopleTableAdapter
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
myTB.Fill(myDS.People)
Dim results As IEnumerable(Of TECHNETDataSet.PeopleRow) = From p As TECHNETDataSet.PeopleRow In myDS.People
For Each result In results
result.City = "Turin"
Next
myTB.Update(myDS.People)
End Sub
End Class
Si è cioè popolato il DataSet, ed estratte, in un'enumerazione di PeopleRow, le righe contenute in tabella. A seguire, tramite un ciclo For/Each, si è modificato il valore di campo, e - tramite Update da TableAdapter - si è eseguito il consolidamento del dato. Eseguendo tale esempio, e controllando la propria tabella su SQL Server, si noterà l'avvenuta modifica dei record.
Un modo più LINQ-like di scrivere quanto sopra è rappresentato dal seguente esempio, in cui modificheremo la città in "Milan", per tutti i records:
Public Class Form1
Dim myDS As New TECHNETDataSet
Dim myTB As New TECHNETDataSetTableAdapters.PeopleTableAdapter
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
myTB.Fill(myDS.People)
Dim results As IEnumerable(Of TECHNETDataSet.PeopleRow) = (From p As TECHNETDataSet.PeopleRow In myDS.People)
results.ToList.ForEach(Sub(x As TECHNETDataSet.PeopleRow) x.City = "Milan")
myTB.Update(myDS.People)
End Sub
End Class
Infine, supponiamo di voler modificare il campo City per un record in particolare. Vogliamo, ad esempio, fare in modo che per il singolo record in cui è presente la stringa "John" nel nome, la città vada impostata a "New York".
Public Class Form1
Dim myDS As New TECHNETDataSet
Dim myTB As New TECHNETDataSetTableAdapters.PeopleTableAdapter
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
myTB.Fill(myDS.People)
Dim p As TECHNETDataSet.PeopleRow = myDS.People.Single(Function(x As TECHNETDataSet.PeopleRow) x.Name.Contains("John") <> 0)
p.City = "New York"
myTB.Update(myDS.People)
End Sub
End Class
In questo caso, tramite il richiamo alla funzione Single, abbiamo estratto un record riferito alla condizione imposta nella funzione interna, ovvero che la stringa John sia presente nella colonna nome. Successivamente, riferendosi alla variabile così inizializzata, sarà sufficiente modificare la proprietà in questione, eseguendo poi la consueta chiamata di aggiornamento del TableAdapter.
Bibliografia
Altre lingue
Il presente articolo è disponibile nelle seguenti traduzioni: