I file XML sono un altro degli ambiti in cui Linq permette di gestire in modo più semplice ed elegante l’estrazione di dati. Personalmente proprio nella gestione dei file XML ho trovato il massimo vantaggio. Con Linq i file xml diventano vere e proprie banche dati visto che è possibile scrivere query complesse su di esse. Non saranno DBMS relazionali, ma sicuramente sono un validissimo metodo per gestire informazioni.
Il namespace da importare per utilizzare Linq con XML è
System.Xml.Linq
All’interno di questo namespace troviamo diversi oggetti, tutti facilmente individuabili dal prefisso X davanti. L’oggetto principale è l’XDocument che rappresenta un documento XML su cui poter andare a fare selezione ed editing di dati.
XDocument document = XDocument.Load(path);
Con questa istruzione avete caricato un file XML presente su disco (file o stream). Il metodo save permette invece di salvarlo (modifiche al file diverranno effettive solo dopo il salvataggio).
Il metodo Parse permette invece di caricare una stringa come XML (nel caso aveste necessità di importare dati XML in altri modi).
Un documento xml è strutturato in nodi e sotto nodi, ecco un esempio
xml version="1.0" encoding="utf-8" ?>
<data>
<contatti>
<contatto id="0" nome="Roby" email="robbydx@fastwebnet.it"/>
contatti>
<messaggi>
<messaggio id="0" ref="0" oggetto="prima email" data="2008-07-09" text="primo messaggio" />
<messaggio id="1" ref="0" oggetto="saluti" data="2008-07-10" text="tutorial" />
messaggi>
data>
Un documento è composto di nodi delimitati da tag
<nomeNodo>
e
nomeNodo>.
All’interno possono trovarsi altri nodi e via dicendo. L’unica regola è che il nodo più esterno deve essere unico (la cosiddetta radice, in inglese root). Ogni nodo contiene informazioni sotto forma di attributi, dati inseriti come
nomeAttributo=”valoreAttributo”
Ogni dato è necessariamente rappresentato come stringa e sarà necessario fare le dovute conversioni per importare ed esportare valori valori.
Esempio
<mynodo nome=”mario”>mynodo>
Nell’esempio ho un attributo nome che ha come valore mario. Se un nodo non contiene nodi o valori allora si può scrivere in maniera contratta
<mynodo nome=”mario”/>
La prima riga del file xml è la codifica. Questo include ad esempio la codifica dei caratteri (la codifica ansi ad esempio permette solo 256 valori escludendo eventuali caratteri non standard come lettere accentate in modo particolari o caratteri stranieri). Per maggiori approfondimenti la rete è ricca di nozioni su xml. Passiamo ora a Linq.
Tutte le classi di Linq per Xml partono da un oggetto base XObject che viene ereditato da un oggetto XNode che rappresenta un nodo. Anche XDocument eredita da XNode (un documento è considerato un nodo radice con la decodifica). Il nodo generico è invece rappresentato dalla classe XElement. I metodi di navigazione per XDocument e XElement sono i medesimi e proprio con queste due classi si interagisce.
Il primo gruppo di metodi da vedere è per ottenere gli elementi figli di un nodo.
- Element : ottiene il primo elemento figlio
- Elements : ottiene tutti gli elementi figli
- ElementsAfterSelf : ottiene gli elementi fratelli (detti comunemente siblings) precedenti al nodo attuale
- ElementsBeforeSelf: ottiene gli elementi fratelli (detti comunemente siblings) precedenti al nodo attuale
Ognuno di questi metodi permette di selezionare i nodi in modo generico oppure specificando un nome di tag tramite un oggetto XName (XName può essere inizializzato direttamente come stringa avendo un operatore di cast implicito).
Ad esempio se un nodo contiene 3 sottonodi di tipo persona , contatto e messaggio
myNode.Elements(“messaggio”) restituirà il solo nodo messaggio.
XName può essere inizializzato in modo più complesso usando il metodo statico get e specificando namespace e nome (solo per file xml più articolati).
La lista restituita dai metodi sarà vuota se non esistono gli elementi richiesti. Gli equivalenti Nodes, NodesAfterSelf sono identici ma lavorano a livello di classi XNode.
La seconda istruzione fondamentale è la Descendants (e DescendantNodes) che restituiscono tutti i nodi sottostanti il nodo a cui si applica. Quindi non solo quelli appena sotto ma andando a cercare fino ai nodi foglia.
Nell’esempio riportato l’istruzione Elements applicata al documento restituirà 1 (il nodo “dati”), mentre se applicato a “dati” restituirà 2 (“contatti” e “messaggi”). Descendants invece darà 6 (il nodo “dati”, “contatti”, “messaggi”, “contatto” ed i 2 “messaggio”).
Essendo questi degli array possono essere utilizzati con Linq per estrarre i dati. Ad esempio
Var contatti = from c in document.Descendants() where p.Name == "contatto" select c;
Questa query ad esempio ci fanno estrarre tutti i contatti dal documento. A questo punto possiamo utilizzare il metodo Attribute per prenderci gli attributi e crearci le nostre entità. Ovviamente potremo utilizzare gli attributi per filtrare sulle ricerche.
var messaggi = from m in document.Descendants() where m.Name == "messaggio" && m.Attribute("id").Value == “0 select m
Qui prendiamo solo i nodi “messaggio” con tag “id” uguale a 0.
Ovviamente potevamo utilizzare un XName in descendants per evitare un controllo. Ecco ad esempio una query Linq che prende tutti i messaggi con i relativi autori.
var total = from c in document.Descendants("contatto")
join m in document.Descendants("messaggio") on c.Attribute("id").Value equals m.Attribute("ref").Value
select new { Nome = c.Attribute("nome").Value, Email = c.Attribute("email").Value, Oggetto = m.Attribute("oggetto").Value, Data = m.Attribute("data").Value };
Infine possiamo utilizzare i metodi Add per aggiungere ad un nodo altri nodi (quindi potete istanziare nuovi XElement ed aggiungerli a quello attuale), remove per rimuoverli o replace per sostituirli con altri.
Demo