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

Introduzione
In questa terza parte della serie di articoli introduttivi dedicati a LINQ introduciamo LINQ-to-Xml (definito anche XLinq), l'area di questa tecnologia che consente di lavorare con i documenti XML. Si tratta di un ambito di particolare importanza, sia perché fornisce una nuova metodologia per la lettura e la scrittura di documenti XML ulteriore al namespace System.Xml già noto, sia perché permette di eseguire interrogazioni sui dati contenuti in questo tipo di documento sfruttando il modello unificato di programmazione implementato dalla tecnologia stessa e costituito dalle query expression.

L'articolo è suddiviso in quattro parti, ognuna delle quali rappresenta un'area concettuale di LINQ-to-Xml. Questa rappresentazione logica non rispecchia, necessariamente, il punto di vista standard nello studio di LINQ ma, piuttosto, è il modo con cui l'autore di questo articolo si è approcciato all'argomento.

L'articolo è corredato, come di consueto, da codice sorgente disponibile in Area Download di VB T&T.
Il codice dimostrativo è costituito da una soluzione contenente, a sua volta, quattro progetti (tre di tipo Console e uno Windows Forms), ciascuno dei quali riferibile ad una particolare area dell'articolo.
Ad ogni buon conto, è conveniente seguire passo per passo le procedure esposte che permetteranno di comprendere i tratti salienti di LINQ-to-Xml e perché altri concetti (come i metodi extension e le espressioni lambda) sono davvero importanti.

Il namespace System.Xml.Linq
.NET Framework 3.5 espone un namespace chiamato System.Xml.Linq, che fornisce l'accesso a una serie di classi e ulteriori caratteristiche di Visual Basic 2008 per lavorare con i documenti XML. Grazie a questo, in LINQ-to-Xml ciascuna parte di un documento Xml è rappresentata da una classe .NET. La seguente tabella riepiloga le principali classi del namespace System.Xml.Linq e gli elementi che esse rappresentano:

Classe Cosa rappresenta
XDocument Un documento XML
XElement Un elemento del documento XML
XAttribute Un attributo XML di un oggetto XElement
XComment Un commento
XCData Una sezione CData nel documento XML
XDeclaration Dichiarazione XML
XNamespace Un namespace XML

Come potete osservare, le classi di LINQ-to-Xml iniziano con la lettera "X", proprio a sottolineare il fatto che si sta lavorando con XLinq. In sostanza, tramite queste classi, è possibile costruire un documento XML completo di nodi, attributi, sezioni CData e quant'altro di interesse. Nella maggior parte dei casi il costruttore delle classi riceve argomenti che rappresentano il valore dell'oggetto che si sta istanziando.
Il seguente codice di esempio crea un documento XML contenente la dichiarazione XML, un commento, un nodo principale e un nodo nidificato in quest'ultimo, decorato con un attributo (i commenti inseriti nel codice dovrebbero aiutare nella comprensione):

        'Istanzia un nuovo documento XML
        Dim documento As New XDocument

        'Assegna la dichiarazione XML al documento
        'es.: <?xml version="1.0" encoding="utf-8" standalone="yes">
        Dim dichiarazione As New XDeclaration("1.0", "utf-8", "yes")
        documento.Declaration = dichiarazione

        'Istanzia e aggiunge un commento
        Dim commento As New XComment("Documento creato con XLinq")
        documento.Add(commento)

        'Istanzia un nuovo nodo Xml
        Dim primoElemento As New XElement("Elenco")

        'Istanzia un nodo Xml che verr\u224 ? nidificato nel precedente
        Dim elementoNidificato As New XElement("Persona")

        'Istanzia un attributo per quest'ultimo nodo e lo aggiunge allo stesso
        Dim attributo As New XAttribute("Nome", "Alessandro")
        elementoNidificato.Add(attributo)

        'Aggiunge al nodo principale quello da nidificare
        primoElemento.Add(elementoNidificato)

        'Aggiunge al documento il nodo principale
        documento.Add(primoElemento)

        'Salva il documento
        documento.Save("C:\Documento.xml")

Riepilogando, abbiamo la seguente situazione:

Per poter verificare il risultato del nostro lavoro, facciamo la conoscenza del metodo Load della classe XDocument che, al contrario della sua controparte Save, è un metodo condiviso (Shared) e non di istanza. Il metodo Load consente di caricare in memoria un documento XML, assegnando il risultato dell'operazione a un oggetto XDocument.
Il seguente esempio mostra come caricare in memoria il documento creato dal precedente frammento, mostrandolo nella finestra della Console:

        'Carica il documento in memoria
        Dim nuovoDocumento As XDocument = XDocument.Load("C:\documento.xml")

        'e lo visualizza
        Console.WriteLine(nuovoDocumento.ToString)
        Console.ReadLine()

Il risultato è chiarito dalla seguente figura:

Una volta compresa la logica di queste classi, creare un documento XML non è un procedimento difficile. Come vedremo in un prossimo paragrafo, l'utilizzo delle query expression permette di recuperare con facilità i dati contenuti nel documento. Tuttavia, l'utilizzo di questa metodologia può essere piuttosto macchinoso e il codice, diventando più lungo, può facilmente diventare di difficile lettura. Fortunatamente Visual Basic 2008 implementa una nuova ed eccezionale caratteristica, costituita dai cosiddetti XML Literals (valori XML letterali).

Valori letterali di Visual Basic 2008
Visual Basic 2008 presenta una importante novità, che tutti gli sviluppatori C# ci invidiano :). Tale novità è rappresentata dagli XML Literals, i cosiddetti valori XML letterali. Tale novità consiste nella possibilità di scrivere tag XML in-line, quindi all'interno del codice Visual Basic. Questo permette di raggiungere diversi obiettivi, come l'estrema facilità di scrittura di documenti XML già all'interno del proprio codice e la possibilità di mantenere molto più ordinato il codice stesso.

Si consideri il seguente frammento, che genera un nuovo documento XML contenente una basilare lista di contatti:

        Dim documento As XDocument = <?xml version="1.0"?>
                                     <Elenco>
                                         <!--Commento: aggiungiamo un paio di persone-->
                                         <Persona Nome="Alessandro" Cognome="Del Sole"/>
                                         <Persona Nome=<%= nome %> Cognome=<%= cognome %>/>
                                     </Elenco>

La dichiarazione esplicita di un oggetto XDocument permette di indicare al compilatore che si sta scrivendo un documento intero e non una sua parte. Questa precisazione è doverosa, nel senso che l'utilizzo degli XML literals può essere effettuato anche nei confronti di oggetti XElement. L'indicazione della dichiarazione XML è obbligatoria nel caso in cui si sta lavorando su oggetti XDocument (altrimenti viene sollevato un errore dall'IDE). Infatti, qualora questa non venga indicata e il tipo (XDocument o XElement) non venga dichiarato esplicitamente ma determinato per inferenza, il compilatore assume che l'oggetto su cui si sta lavorando sia di tipo XElement.

Per quanto comoda e utile, questa metodologia, così come l'abbiamo vista, avrebbe poca utilità dal momento che obbligherebbe lo sviluppatore a conoscere tutti i dati di cui ha bisogno (c.d. hard coding), mentre invece questi dati spesso debbono essere recuperati da database, da insiemi di oggetti o da variabili il cui valore viene determinato durante il ciclo di vita dell'applicazione. A questo proposito Visual Basic 2008 fornisce un'ulteriore caratteristica, costituita dalle espressioni incorporate (definite, in gergo, embedded expressions). Le espressioni incorporate consentono di inserire all'interno dei tag XML valori contenuti in altri oggetti, quindi valori che lo sviluppatore non conosce ma che determina tramite codice.
Un esempio pratico sarà sicuramente chiarificatore. Aggiungeremo al documento XML sopra mostrato un ulteriore elemento chiamato Persona, per il quale i valori degli attributi Nome e Cognome verranno letti da variabili. In primo luogo, dichiariamo i due campi:

        'Un nome di fantasia
        Dim nome As String = "Mario"
        Dim cognome As String = "Rossi"

In secondo luogo, modifichiamo il documento XML come segue:

        Dim documento As XDocument = <?xml version="1.0"?>
                                     <Elenco>
                                         <!--Commento: aggiungiamo un paio di persone-->
                                         <Persona Nome="Alessandro" Cognome="Del Sole"/>
                                         <Persona Nome=<%= nome %> Cognome=<%= cognome %>/>
                                     </Elenco>

Un'espressione incorporata è racchiusa tra i simboli <%= e %>. In questo caso abbiamo incorporato dei campi di tipo String (piuttosto che conoscere in anticipo i valori di cui si ha necessità), in un successivo paragrafo utilizzeremo in dettaglio le embedded expression per generare elementi XML in maniera dinamica a partire da una tabella di un database SQL.

Utilizzando il metodo Save, e successivamente il metodo Load della classe XDocument, è possibile memorizzare su disco il documento creato e leggerne poi il contenuto:

        documento.Save("C:\documento.xml")

        Console.WriteLine(XDocument.Load("C:\documento.xml").ToString)
        Console.ReadLine()

Il risultato a video sarà il seguente:

Ci addentriamo ora un po' di più nello specifico di LINQ-to-Xml per osservare come utilizzare le relative tecniche di programmazione per estrarre dati da documenti XML esistenti. Osserveremo, in questa fase, l'esistenza di appositi metodi e proprietà extension che facilitano l'estrazione dei dati e il successivo data-binding nei confronti di altri oggetti .NET.

Interrogare documenti XML esistenti
LINQ-to-Xml mette a disposizione le metodologie già viste in LINQ-to-Objects e in LINQ-to-SQL per interrogare i dati, con l'aggiunta di specifici metodi e proprietà extension dedicati ai documenti XML. In questo paragrafo impareremo a caricare in memoria un documento XML esistente e ad interrogarlo per estrarre alcuni dati. Successivamente vedremo come unire i dati estratti dal documento a quelli estratti da un secondo documento XML a struttura analoga, collegando il risultato dell'unione ad alcuni controlli per la visualizzazione dei dati. Infine, vedremo l'utilizzo pratico di una espressione lambda, per meglio comprenderne l'utilità.

In primo luogo è necessario creare un'applicazione Windows Forms, che costituisce la base del nostro lavoro. L'oggetto Form conterrà un controllo BindingNavigator e un controllo DataGridView, che devono essere trascinati dalla toolbox alla superficie del designer senza eseguire alcun data-binding preliminare (lo faremo, infatti, da codice). L'aspetto assunto in fase di design è il seguente:

Al progetto aggiungiamo un primo documento XML (che chiameremo ContattiPrimo.xml), che rappresenta una lista semplificata di contatti:

<?xml version="1.0" encoding="utf-8" ?>

<Contatti>
  <Contatto Nome="Alessandro" Città="Cremona"/>
  <Contatto Nome="Diego" Città="Trieste"/>
  <Contatto Nome="Antonio" Città="Milano"/>
  <Contatto Nome="Mario" Città="Belluno"/>
  <Contatto Nome="Gianluca" Città="Firenze"/>
</Contatti>

Se utilizzate Visual Basic 2008 Express Edition, l'unico modo per aggiungere un documento XML è selezionare un file di testo e cambiarne l'estensione in .xml. L'IDE riconoscerà automaticamente il tipo di documento e metterà a disposizione l'apposito Intellisense. È importante ricordare di impostare, tramite la Finestra delle proprietà, le proprietà Copia nella directory di output e Operazione di compilazione rispettivamente su Copia sempre e Contenuto, al fine di includere il documento nell'output del progetto.

Si passa poi alla scrittura di codice managed, attraverso cui vorremo estrarre i dati relativi a ciascun contatto. Il codice seguente andrà inserito, a scopo dimostrativo, nell'evento Load dell'oggetto Form. In primo luogo, si carica in memoria il documento:

        'Carica il documento
        Dim doc As XDocument = XDocument.Load(Application.StartupPath + "\ContattiPrimo.xml")

La fase successiva può essere rappresentata dalla possibilità di utilizzare una query expression per estrarre ciascun elemento contenuto nel documento XML (quindi ciascun contatto) e i relativi attributi. Il risultato dell'interrogazione sarà poi assegnato ad una collection, come già visto in altre occasioni. Ciò posto, scriviamo la seguente query expression in merito alla quale seguiranno alcune osservazioni:

        'Estrae tutti gli elementi <Contatto> contenuti nell'elemento <Contatti>
        Dim risultato = From persona In doc...<Contatti>.<Contatto> _
                        Select Nome = persona.@Nome, Città = persona.@Città

Come potete osservare, LINQ prevede una simbologia specifica per le interrogazioni eseguite nell'ambito dei documenti XML, proprio per la particolare struttura che contraddistingue questo tipo di file. Il seguente elenco riepiloga il significato dei simboli presenti nella sopra esposta query expression:

Quindi, il valore di ciascun attributo è stato assegnato ad un campo, tramite la clausola Select (Nome e Città). Questa selezione multipla di valori confluisce in un nuovo tipo anonimo. Pertanto, il risultato dell'interrogazione è costituito da una collezione di tipi anonimi. Questo particolare tipo di dato è stato determinato dal compilatore per inferenza (vi basterà passare col puntatore del mouse sulla dichiarazione della variabile risultato ed osservare la tooltip che descrive la variabile stessa come un oggetto di tipo System.Collections.Generic.IEnumerable(Of tipo anonimo)).

L'utilizzo dei tipi anonimi non deve comunque spaventare, dal momento che la collection così costituita può essere trattata come tutte le altre. Per esporre in modo gradevole il risultato della nostra interrogazione, possiamo collegare, tramite binding, il risultato dell'interrogazione stessa agli oggetti per la visualizzazione dei dati precedentemente aggiunti all'applicazione.

In primo luogo, quindi, si istanzia un oggetto BindingSource che costituirà la sorgente dati. Le due seguenti righe di codice sono successive a quelle sopra illustrate:

        Dim source As New BindingSource
        source.DataSource = risultato.ToList

L'oggetto risultato dispone di un metodo extension chiamato ToList, che trasforma il contenuto dell'oggetto stesso in un altro oggetto di tipo List(Of T) e che, quindi, può essere collegato alla sorgente dati. Sarà ora sufficiente scrivere quanto segue per completare il data-binding:

        BindingNavigator1.BindingSource = source
        DataGridView1.DataSource = source

Il risultato, una volta avviata l'applicazione, è il seguente:

In questo modo abbiamo presentato il risultato della query su un documento XML in un controllo Windows Forms.
Ma cosa succede se abbiamo diversi file XML a struttura analoga, ciascuno contenente dati di nostro interesse? Potremmo, ad esempio, avere la necessità di selezionare parte dei dati da un file e parte da un altro, per poi presentare il risultato complessivo. Per vedere come fare, aggiungiamo al progetto un nuovo documento XML, chiamato ContattiSecondo.xml, la cui struttura può essere di questo tipo:

<?xml version="1.0" encoding="utf-8" ?>
 <Contatti>
  <Contatto Nome="Luciano" Citt\u224 ?="Padova"/>
  <Contatto Nome="Maurizio" Citt\u224 ?="Milano"/>
  <Contatto Nome="Renato" Citt\u224 ?="Varese"/>
  <Contatto Nome="Sergio" Citt\u224 ?="Vicenza"/>
</Contatti>

Poiché questo file è strutturato come il precedente, utilizziamo un code snippet simile per leggere i dati che esso contiene:

        Dim secondDoc As XDocument = XDocument.Load(Application.StartupPath + "\ContattiSecondo.xml")

        Dim secondoRisultato = From persona In secondDoc...<Contatti>.<Contatto> _
                               Select Nome = persona.@Nome, Città = persona.@Città

Anche in questo caso il risultato confluisce in una collezione di tipi anonimi (oggetto secondoRisultato). Gli oggetti restituiti dalle query expression espongono un metodo extension molto utile, chiamato Union, che permette di unire due risultati in un'unica collection. Nel seguente frammento di codice uniremo i due risultati, eseguendo, inoltre, l'ordinamento dell'unione sfruttando un'espressione lambda di cui daremo cenno più avanti:

        Dim unione = risultato.Union(secondoRisultato).OrderBy(Function(Contatto) Contatto.Nome)

Il metodo Union, estensione dell'oggetto risultato, unisce a quest'ultimo il contenuto dell'oggetto secondoRisultato. Il metodo extension OrderBy consente di eseguire l'ordinamento della collection in modo similare alla clausola Order By utilizzabile nelle query expression, con la differenza che riceve, come argomento, una espressione lambda. Poiché stiamo lavorando su una collection di tipi anonimi, l'argomento ricevuto dall'espressione lambda è, per l'appunto, un tipo anonimo determinato per inferenza. Il compilatore sa che l'argomento di tipo anonimo è riferibile alla nostra collection e ci permette, tramite Intellisense, di stabilire quale proprietà sarà presa come base di confronto per l'ordinamento (in questo caso la proprietà Nome). Se avete curiosità di capire il tipo di dato risultante da queste operazioni, vi basterà passare col puntatore del mouse sulla dichiarazione dell'oggetto unione per osservare come questo, per inferenza, sia stato qualificato come System.Collections.Generic.IOrderedEnumerable(Of tipo anonimo).

Possiamo sfruttare ancora una volta il metodo extension ToList per esporre il risultato del nostro lavoro attraverso i precedenti controlli data-bound:

        source.DataSource = unione.ToList

        BindingNavigator1.BindingSource = source
        DataGridView1.DataSource = source

Tutto questo, all'avvio dell'applicazione, si traduce nella seguente rappresentazione:

L'ultima fase dell'articolo prenderà in considerazione un esempio in cui verranno letti i dati contenuti in una tabella di un database SQL e riversati in un documento XML generato in modo dinamico. Vedremo così in azione le embedded expression e la possibilità di integrare più aree di LINQ.

Creare documenti XML, con esecuzione di query su database e utilizzo di espressioni incorporate
Questa parte dell'articolo presuppone che abbiate letto la precedente puntata, introduttiva di LINQ-to-SQL. Utilizzeremo infatti, in via preliminare, diverse tecniche illustrate in quella sede. Il progetto dimostrativo preso in considerazione è costituito da un'applicazione Console collegata al database Northwind (anche in questo caso il database non è incluso nel progetto, per motivi legati alla distribuzione, ma potete scaricarlo da questo indirizzo). Una volta stabilita la connessione al database, bisogna aggiungere una classe LINQ-to-SQL al progetto denominandola Northwind.dbml e trascinare sulla sua superficie la tabella Employees del database stesso, in modo che il designer si presenti in questo modo:

Sempre utilizzando le modalità descritte nel precedente articolo, è necessario collegare l'applicazione alla classe di mapping chiamata Employees generata da LINQ-to-SQL. Lo scopo dell'applicazione di esempio è quello di estrarre dalla tabella Employees tutti gli impiegati il cui cognome inizi con la lettera "D", ordinare il risultato e creare un documento XML che contenga tanti elementi quanti sono gli impiegati così estratti. In primo luogo, quindi, si esegue l'interrogazione sull'entity, ricorrendo al DataContext:

Module Module1

    Dim dc As New NorthwindDataContext

    Sub Main()

        'Recupera dalla tabella Employees del database Northwind
        'tutti gli impiegati il cui cognome inizia con la lettera "B"
        'ordinando il risultato in base al nome di battesimo
        Dim impiegati = From impiegato In dc.Employees _
                        Where impiegato.LastName.StartsWith("D") _
                        Order By impiegato.FirstName _
                        Select impiegato

La collection Impiegati contiene tutti gli oggetti Impiegato il cui cognome inizi con la lettera "D". Supponiamo, ora, di voler generare un documento XML che contenga la lista degli impiegati così estratta, specificando, per ciascuno di essi, solo alcune proprietà come il nome, il cognome, lo stato e la città di residenza. Il codice da utilizzare è il seguente:

        'Genera un nuovo documento XML contenente tutti gli impiegati selezionati
        'mediante la query precedente
        'Le embedded expression generano un nuovo nodo per ciascun impiegato
        Dim documento As XDocument = <?xml version="1.0"?>
                                     <Impiegati>
                                         <%= From persona In impiegati Select _
                                             <Impiegato Nome=<%= persona.FirstName %>
                                                 Cognome=<%= persona.LastName %>
                                                 Stato=<%= persona.Country %>
                                                 Città=<%= persona.City %>/> %>
                                     </Impiegati>

La potenza delle espressioni incorporate consiste nella possibilità di includere delle query al loro interno, oltre che includere valori costanti (come visto in un esempio precedente). La query scritta nell'espressione incorporata appena vista crea un elemento XML chiamato Impiegato per ciascuna persona presente nella collection Impiegati. Per ciascun elemento XML Impiegato, vengono specificati attributi mediante l'utilizzo di ulteriori espressioni incorporate che estraggono i valori delle proprietà di ciascun oggetto estratto.

L'utilizzo del metodo Save della classe XDocument e il successivo utilizzo del metodo Load ci consentono, rispettivamente, di salvare su disco e caricare in memoria il risultato del nostro lavoro:

        documento.Save("C:\Impiegati.xml")

        documento = XDocument.Load("C:\impiegati.xml")
        Console.WriteLine(documento)

        Console.ReadLine()
    End Sub
End Module

All'avvio, l'applicazione apparirà come segue (dopo qualche secondo di attesa necessario alla connessione al database):

Appare evidente come le novità sintattiche di Visual Basic 2008 e la potenza delle embedded expression consentano di scrivere dati in formato XML con estrema versatilità, anche a partire da sorgenti dati diverse e complesse.

Conclusioni
LINQ riesce a rendere decisamente più semplice il lavoro di lettura, manipolazione e scrittura dei dati. LINQ-to-XML è un altro importante aspetto di questa tecnologia, che, in aggiunta, offre specifici vantaggi agli sviluppatori Visual Basic che hanno la necessità (o la preferenza) di archiviare i propri dati nel formato XML.

Vi ricordo, infine, che sul mio blog sono disponibili alcuni screen-cast che ho realizzato in merito a LINQ-to-XML e che potete scaricare liberamente. Per ulteriori informazioni potete contattarmi al mio indirizzo visitare il mio blog.