Introduzione a LINQ: LINQ-to-DataSet
a cura di Alessandro Del Sole (requisiti: conoscenze approfondite di .NET Framework)

Introduzione
La quarta e ultima parte della serie di articoli introduttivi a LINQ riguarda l'attività di interrogazione e manipolazione dei dati attraverso l'intervento sugli oggetti DataSet che, come noto, rappresentano, in maniera fortemente tipizzata, porzioni del database a cui la propria applicazione è collegata. Tale fattispecie è definita LINQ-to-DataSet e trae pieno vantaggio dal modello di programmazione già sfruttato negli altri ambiti di LINQ visti sinora.

In questa sede ci occuperemo di creare un'applicazione di esempio collegata al consueto database SQL Northwind. Nell'applicazione verrà stabilita una relazione tra due tabelle: Employees e Orders. Lo scopo del codice dimostrativo sarà quello di monitorare tutti gli ordini riferibili a ciascun impiegato dell'ipotetica azienda per la quale il database è progettato, nonché quello di filtrare l'elenco degli impiegati in base a dei criteri prestabiliti. I dati così predisposti verranno interrogati e filtrati tramite LINQ-to-DataSet. Pertanto, due sono i prerequisiti necessari per poter proseguire nella lettura: l'aver scaricato il database in questione, che potete trovare a questo indirizzo e, ovviamente, la presenza di SQL Server 2005 Express Edition. Il codice sorgente che accompagna l'articolo, invece, è disponibile in Area Download di VB T&T.

Creazione dell'applicazione di esempio
Dopo aver aperto Visual Studio 2008 (o Visual Basic 2008 Express Edition), create un nuovo progetto Windows Forms per Visual Basic. La prima operazione da compiere è quella che permette di collegare il database all'applicazione. Se non avete particolari esperienze al riguardo, una buona lettura preliminare è costituita da questo articolo di Luciano Bastianello. Successivamente, eseguite la seguente sequenza di operazioni:

  1. dal menu Dati, selezionate il comando Aggiungi nuova origine dati. Quando appare il wizard, selezionate l'oggetto Database e poi fate clic su Avanti;
  2. nella seconda schermata del wizard, fate clic su Nuova connessione. Al comparire della finestra Aggiungi connessione selezionate il provider di dati per Microsoft SQL Server, quindi sfogliate le cartelle del vostro hard disk al fine di selezionare il database Northwind;
  3. andate avanti nelle varie maschere del wizard fino ad arrivare a quella in cui viene mostrato l'elenco degli elementi costituenti il database (tabelle, stored procedures, visualizzazioni). Al comparire di questa finestra selezionate le tabelle Employees (impiegati) e Orders (ordini). Questa selezione multipla permetterà di stabilire una relazione tra le due tabelle (uno a molti). Lasciate invariato l'identificatore del DataSet proposto per default e completate la procedura guidata.

Una precisazione: se vi incuriosiscono le novità, in alternativa potete provare a collegare all'applicazione anche la versione per SQL Server Compact Edition del database Northwind, fornita con Visual Studio. Le tecniche illustrate nell'articolo avranno il medesimo risultato (può cambiare qualche identificatore, nel qual caso potete farvi aiutare dall'Intellisense). Dopo aver eseguito la predetta sequenza di applicazioni, il designer del nostro DataSet si presenta come in figura:

Successivamente è necessario trascinare sul form principale gli oggetti che rappresentano le tabelle, al fine di perfezionare il data-binding. In primo luogo, quindi, bisogna trascinare dalla finestra Origini dati l'oggetto Employees, implementato nel DataSet. La figura seguente rappresenta la finestra Origini dati, all'interno della quale è possibile notare il rapporto di dipendenza dell'oggetto Orders dall'oggetto Employees:

L'operazione di drag'n'drop dell'oggetto Employees aggiungerà alla finestra di progettazione anche una serie di controlli per l'accesso ai dati, tra i quali spiccano un controllo chiamato EmployeesBindingSource (che costituisce la prima sorgente dati) e uno chiamato EmployeesBindingNavigator che consentirà di sfogliare i dati tramite l'apposita barra degli strumenti.

In secondo luogo, trascinate l'oggetto Orders. Ingrandite, poi, le due DataGridView generate dall'IDE e posizionatele in modo che consentano una visione il più possibile chiara dei dati. Da ultimo, aggiungete un controllo Label tra le due DataGridView. Questa Label ci servirà per visualizzare il risultato delle operazioni svolte con LINQ-to-DataSet.
In sostanza, il risultato della fase di design è simile a quanto riportato nella seguente figura:

Avviando l'applicazione si potrà subito vedere il risultato del data-binding. Come si può infatti osservare dalla seguente figura, utilizzando i comandi della toolbar o selezionando i singoli impiegati nella DataGridView superiore, sarà possibile visionare gli ordini (nella parte sottostante) riferibili all'impiegato selezionato, relazione consentita dalla proprietà EmployeeID che le tabelle aggiunte inizialmente al DataSet hanno in comune.
Tutto ciò, però, è solo la base su cui lavorare. Ci occuperemo, infatti, di evidenziare il totale degli ordini che ciascun impiegato ha gestito, tramite alcune query expression di LINQ.

Interrogare il DataSet con LINQ in Visual Basic
Il lavoro da svolgere può essere schematizzato in alcuni passaggi:

  1. attendere che l'utente selezioni l'impiegato di interesse nella DataGridView superiore;
  2. intercettare l'evento corrispondente;
  3. eseguire la query expression;
  4. mostrare il risultato della query.

In primo luogo è necessario attivare l'editor di codice. Fatto questo, è necessario considerare quale evento vada intercettato. La scelta più opportuna è quella di intercettare l'evento CurrentChanged dell'oggetto EmployeesBindingSource, che si verifica ogni qual volta si fa clic su una riga della DataGridView relativa alla tabella contenente l'elenco degli impiegati che, in altre parole, è la DataGridView collegata all'oggetto EmployeesBindingSource. Poiché quest'ultimo oggetto costituisce la sorgente dati, sarà proprio questo a generare l'evento di nostro interesse.

Iniziamo, quindi, con lo scrivere il gestore dell'evento CurrentChanged, all'interno del quale faremo utilizzo di un comodo code snippet fornito a corredo di Visual Basic 2008, chiamato Converti BindingSource.Current in una specifica riga di un DataTable. Per aggiungere il frammento di codice tramite Intellisense, fate clic destro e selezionate il comando Inserisci frammento di codice del menu contestuale. Aprite la cartella di snippet chiamata Dati - LINQ, XML, finestra di progettazione, ADO.NET, quindi la sottocartella chiamata Funzionalità della finestra di progettazione e ADO.NET. Il frammento di codice sopra menzionato comparirà nell'editor. Modificatelo nel modo seguente:

    Private Sub EmployeesBindingSource_CurrentChanged(ByVal sender As Object, _
                                                      ByVal e As System.EventArgs) _
                                                      Handles EmployeesBindingSource.CurrentChanged

        'Il seguente frammento \u232 ? contenuto nell'archivio di code snippet a corredo
        'di Visual Basic e consente di convertire una riga all'interno della sorgente dati
        'in una riga specifica
        Dim row As NORTHWNDDataSet.EmployeesRow
        row = CType(CType(Me.EmployeesBindingSource.Current, DataRowView).Row, _
                          NORTHWNDDataSet.EmployeesRow)

Questo, in parole povere, ci serve per ottenere un riferimento alla riga selezionata dall’utente nella DataGridView. Il gestore dell’evento può essere completato nel modo seguente, che commenteremo di seguito:

        'Calcola il totale degli ordini per l'impiegato selezionato
        Dim total = Aggregate ordine In Me.NORTHWNDDataSet.Orders _
                   Where ordine.EmployeeID = row.EmployeeID _
                   Into Count()

        Label1.Text = String.Concat("L'impiegato selezionato ha curato nr.:", total.ToString, _
                                    " ordini")

    End Sub

Innanzitutto si osservi come l'oggetto su cui si sta eseguendo la query expression sia proprio il DataSet, rappresentato dall'identificatore NORTHWNDDataSet. In particolare stiamo interrogando la sua proprietà Orders, che rappresenta la tabella degli ordini. Il significato della query è così definito: per ciascun ordine contenuto nella tabella Orders, laddove l'identificativo dell'impiegato che ha gestito l'ordine stesso (ordine.EmployeeID) sia uguale all'identificativo dell'impiegato selezionato nella riga della DataGridView considerata (row.EmployeeID) aggiunge l'ordine (Aggregate) al conteggio totale (Into Count). Count è un metodo extension che rappresenta il numero totale degli elementi considerati. Il risultato della query expression viene quindi mostrato nella Label. Se avviate l'applicazione otterrete un risultato simile alla seguente figura:

Abbiamo così raggiunto l'obiettivo di mostrare il totale degli ordini riferibili a ciascun impiegato selezionato, eseguendo la query expression di LINQ sul DataSet. Per completare l'applicazione di esempio filtreremo "a monte" l'elenco degli impiegati dei quali vogliamo avere notizie.

Filtrare i dati di una tabella
L'obiettivo che ci proponiamo ora è quello di ridurre l'elenco degli impiegati dei quali visualizzare gli ordini gestiti, filtrando la relativa tabella e presentando i dati nella DataGridView collegata. Aggiungete, quindi, un pulsante all'applicazione. Nel codice allegato il pulsante è stato aggiunto alla barra degli strumenti del controllo BindingNavigator, ma va bene un qualunque pulsante. Dobbiamo gestire l'evento Click del pulsante affinché, alla sua pressione, la tabella degli impiegati venga filtrata. Supponiamo, quindi, di voler estrarre solo gli impiegati il cui nome di battesimo inizia con la lettera "A":

    Private Sub ToolStripButton1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) _
                                       Handles ToolStripButton1.Click

        'La query expression estrae solo gli impiegati il cui nome di battesimo
        'inizia con la lettera "A"
        Dim impiegatiDiInteresse = From impiegati In NORTHWNDDataSet.Employees _
                                   Where impiegati.FirstName.StartsWith("A") _
                                   Select impiegati

Risulta evidente come, anche in questo caso, la query expression vada a riguardare il DataSet, in particolare la proprietà Employees (che, come noto, rappresenta la tabella degli impiegati) della classe NORTHWNDDataSet. L'espressione è abbastanza semplice e non ha bisogno di ulteriori particolari commenti; può comunque interessare il fatto che il compilatore abbia determinato, per inferenza, il tipo EnumerableRowCollection(Of EmployeesRow) che caratterizza l'oggetto impiegatiDiInteresse. Questo ed altri particolari tipi di dato, nonché alcuni metodi extension specifici per LINQ-to-DataSet, sono implementati nell'assembly System.Data.DataSetExtensions.dll.

Quello che diventa ora importante è capire come il risultato dell'interrogazione debba alimentare la sorgente dati. Sicuramente l'oggetto impiegatiDiInteresse deve costituire il valore della proprietà DataSource dell'oggetto EmployeesBindingSource (poiché questo alimenta la DataGridView che espone i dati relativi alla tabella degli impiegati). Un'assegnazione di questo tipo, però, costituirebbe un errore di casting non valido:

        EmployeesBindingSource.DataSource = impiegatiDiInteresse

Esiste, al riguardo, un metodo extension chiamato AsDataView che restituisce il risultato dell'interrogazione in un formato adatto alla sorgente dati e alla successiva visualizzazione nella DataGridView:

        'Affinché il risultato dell'interrogazione possa essere associato alla
        'sorgente dati, si utilizza il metodo extension "AsDataView"
        EmployeesBindingSource.DataSource = impiegatiDiInteresse.AsDataView

Avviando l'applicazione e facendo clic sul pulsante implementato si otterrà il seguente risultato:

L'applicazione continuerà a mostrare il totale degli ordini riferibili a ciascun impiegato, ma gli impiegati stessi saranno solo quelli provenienti dall'interrogazione effettuata.

Conclusioni
Con l'introduzione a LINQ-to-DataSet concludiamo la panoramica sulla principale novità di .NET Framework 3.5.
Come avete avuto modo di osservare, LINQ offre un modello di programmazione la cui logica di base è la stessa a seconda delle diverse tipologie di sorgenti dati, consentendo allo sviluppatore di utilizzare le nozioni in suo possesso, qualunque sia la forma dei dati da interrogare. In articoli futuri potremo quindi concentrarci su aspetti più specifici e meno didattici di questa importante tecnologia.

Vi ricordo, infine, che sul mio blog è disponibile uno screen-cast dedicato a LINQ-to-DataSet, oltre alla serie di altri screen-cast che ho realizzato in merito a LINQ e che potete scaricare liberamente. Per ulteriori informazioni potete contattarmi al mio indirizzo visitare il mio blog.