LINQ TO XML

Scritto da  Alessandro Mostarda il mercoledì 15 giugno 2011
Linguaggio: C#,VB   •  Framework: 3.5,4.0   •  Livello: 100

Download sorgenti


Linq to XML è un sottoinsieme di LINQ che consente di interrogare file XML sfruttando appieno la potenza di LINQ. Esso è stato introdotto con il framework 3.5 e facilita notevolmente il recupero, la scrittura e, soprattutto, le query su un file XML.

Linq to XML  lavora completamente in memoria ed è per questo che è possibile sfruttare la normale sintassi LINQ per operare su un file.

Rispetto alle precedenti API (DOM) per operare su file XML, LINQ to XML presenta una serie di vantaggi non da poco:

  1. L'utilizzo di metodi statici per accedere ad un file, piuttosto che metodi di istanza.
  2. La possibilità di lavorare direttamente sugli ELEMENT di un file, piuttosto che aprire il file e scorrerlo fino all'ELEMENT che ci interessa. Ciò, ad esempio, ci consente di creare un file XML partendo direttamente dagli ELEMENT, piuttosto che partire dall'oggetto XDocument.
  3. Una maggiore semplicità nell'utilizzo dei namespace.
  4. La maggiore comprensione nel caso in cui si debbano mischiare query verso DB(EntityFramework) insieme a query XML. Infatti entrambe le tecnologie adottano LINQ , per cui un metodo che interroga un BD e restituisce un file XML, risulterà molto più chiaro da scrivere e comprendere.

 La classe principale di LINQ to XML è XDocument, ma abbiamo un insieme di classi, ognuna delle quali rappresenta una parte specifica di un file XML.

  • XElement, rappresenta un elemento;
  • XAttribute, rappresenta un attributo;
  • XNode, rappresenta il concetto(astratto) di un nodo;
  • XNamespace, rappresenta un namespace;
  • XComment, rappresenta un commento;
  • XDeclaration, rappresenta invece la parte dichiarativa del file.

Ma iniziamo, ora ad addentrarci nel codice per vedere all'opera il nostro LINQ to XML.

Come sorgente dei nostri dati utilizzeremo il seguente file:

<?xml version="1.0" encoding="utf-8" ?>
<Catalog>
  <Shoe id="NKAM">
    <Manufacturer>Nike</Manufacturer>
    <Model>Air Max</Model>
    <AvailableSize>
      <Size>38</Size>
      <Size>39</Size>
      <Size>40</Size>
      <Size>41</Size>
      <Size>42</Size>
      <Size>43</Size>
    </AvailableSize>
    <Price>78,00</Price>
  </Shoe>
  <Shoe id="NKSX">
    <Manufacturer>Nike</Manufacturer>
    <Model>Air Shoxx</Model>
    <AvailableSize>
      <Size>38</Size>
      <Size>41</Size>
      <Size>44</Size>
    </AvailableSize>
    <Price>67,00</Price>
  </Shoe>
  <Shoe id="ADGA">
    <Manufacturer>Adidas</Manufacturer>
    <Model>Gazelle</Model>
    <AvailableSize>
      <Size>38</Size>
      <Size>39</Size>
      <Size>42</Size>
      <Size>43</Size>
    </AvailableSize>
    <Price>90</Price>
  </Shoe>
</Catalog>

 

Tanto per iniziare vediamo come creare un oggetto XDocument da un file:

C#

private Document LoadFile()
{
    return XDocument.Load("Shoes.xml");
}

 

VB.NET

Private Function LoadFile() As XDocument
    Return XDocument.Load("Shoes.xml")
End Function

 

Questo codice viene riportato, non tanto per la sua complessità J, ma perchè negli esempi successivi verrà sempre utilizzato.

Un XDocument può essere caricato anche da uno stream, da un Uri, oppure da una stringa che contiene XML.

 Negli esempi che seguono, invece, vedremo l'utilizzo dei metodi Elements, Attributes , Descendants e Nodes, i quali, restituendo un IEnumerable, permettono di conseguenza di restituire ulteriori nodi, elementi e/o attributi.

Ad esempio volendo ritornare l'elenco completo delle gli articoli presente nel file XML, la sintassi sarà la seguente.

C#

public List<Shoe> GetAll()
{
    var xDoc = LoadFile();
 
    var elements = xDoc.Descendants("Shoe");
    return elements.Select(s => new Shoe()
    {
        Id = s.Attribute("id").Value,
        Manufacturer = s.Element("Manufacturer").Value,
        Model = s.Element("Model").Value,
        Price = Convert.ToDecimal(s.Element("Price").Value),
        AvailableSize = s.Descendants("Size").Select(size => (int)size).ToList()
     }).ToList();           
}
 

 

 VB.NET

Public Function GetAll() As List(Of Shoe) Implements ILinqToXml.GetAll
    Dim xDoc = LoadFile()
    Dim elements = xDoc.Descendants("Shoe")
    Return elements.Select(Function(s) New Shoe With
            {
                .Id = s.Attribute("id").Value,
                .Manufacturer = s.Element("Manufacturer").Value,
                .Model = s.Element("Model").Value,
                .Price = Convert.ToDecimal(s.Element("Price").Value),
                .AvailableSize = s.Descendants("Size").Select(Function(size) Convert.ToInt32(size.Value)).ToList()
            }).ToList()
End Function

 

Dall'esempio precedente si può notare l'utilizzo dei metodo Descendants e Elements. In questo caso il metodo Descendants, torna tutti gli elementi SHOE, anche se essi non sono figli dell'oggetto XDocument, al quale viene applicato il metodo stesso, per cui si deduce che tale metodo serve a trovare tutti gli elementi che hanno il nome "Shoe" a prescindere dal livello gerarchico all'interno del file. Infatti, se invece di Descendants avessimo usato il metodo Elements, il risultato sarebbe stato un elenco vuoto. Il metodo Element utilizzato per costruire l'oggetto SHOE dimostra ancora più chiaramente tale concetto. Infatti il metodo Element torna solo i nodi figli dell'oggetto a cui viene applicato il metodo.

 Il prossimo esempio ritorna tutte gli articoli che hanno un prezzo superiore ad un determinato valore.

C#

public List<Shoe> GetByPrice(decimal price)
{
    var xDoc = LoadFile();
    var elements = xDoc.Descendants("Shoe").Where(w => Convert.ToDecimal(w.Element("Price").Value > price);
    return elements.Select(s => new Shoe()
    {
        Id = s.Attribute("id").Value,
        Manufacturer = s.Element("Manufacturer").Value,
        Model = s.Element("Model").Value,
        Price = Convert.ToDecimal(s.Element("Price").Value),AvailableSize = s.Descendants("Size").Select(size => (int)size).ToList()
     }).ToList();   
}

 

 VB.NET

Public Function GetByPrice(ByVal price As Decimal) As List(Of Common.Shoe) Implements ILinqToXml.GetByPrice
    Dim xDoc = LoadFile()
 
    Dim elements = xDoc.Descendants("Shoe").Where(Function(w) Convert.ToDecimal(w.Element("Price").Value) > price)
 
    Return elements.Select(Function(s) New Shoe With
    {
        .Id = s.Attribute("id").Value,
        .Manufacturer = s.Element("Manufacturer").Value,
        .Model = s.Element("Model").Value,
        .Price = Convert.ToDecimal(s.Element("Price").Value),
        .AvailableSize = s.Descendants("Size").Select(Function(size) Convert.ToInt32(size.Value)).ToList()
    }).ToList()
End Function

 

I 2 esempi precedenti sono già più che sufficienti a dimostrare il funzionamento di LINQ to XML per effettuare query verso un file.

Ma linq to XML viene utilizzato non solo in lettura, ma anche in scrittura e quindi ora cominciamo a vedere come creare un semplice file per poi vedere come costruirne uno partendo da zero.

C#

public string CreateXml()
{
    var shoes = this.GetByPrice(70);
 
    var xDoc = new XDocument(
        new XElement("Shoes", shoes.Select(s => new XElement("shoe",
        new XElement("Model", s.Manufacturer + " " + s.Model),
        new XElement("Price", s.Price.ToString("#,##0.00"))))));
   
    return xDoc.ToString();
}

 

VB.NET

Public Function CreateXml() As String Implements ILinqToXml.CreateXml
    Dim shoes = Me.GetByPrice(70)
    Dim xDoc = New XDocument(
        New XElement("Shoes", shoes.Select(Function(s) New XElement("shoe",
        New XElement("Model", s.Manufacturer + " " + s.Model),
        New XElement("Price", s.Price.ToString("#,##0.00"))))))
 
    Return xDoc.ToString()
End Function

 

La cosa che si può notare in  questo esempio è anche la chiarezza, in quanto l'indentazione facilita la comprensione del codice. Questa cosa non era possibile con i vecchi oggetti DOM.

 Ed ora veniamo alle operazione che possono effettuate sui singoli record, ossi inserimento, modifica, cancellazione(CRUD Operations).

 L'inserimento ha un funzionamento molto simile all'esempio precedente.

C#

public void Add()
{
    var shoe = new Shoe()
    {
        Id = "456",
        Manufacturer = "Reebok",
        Model = "Pump",
        Price = 34,
        AvailableSize = new List<int>() { 38, 39, 48 }
    };
 
    var xDoc = this.LoadFile();
           
    xDoc.Element("Catalog").Add(new XElement("Shoe",
        new XAttribute("id", shoe.Id),
        new XElement("Manufacturer", shoe.Manufacturer),
        new XElement("Model", shoe.Model),
        new XElement("Price", shoe.Price.ToString("#,##0.00")),
        new XElement("AvailableSize",
                     shoe.AvailableSize.Select(s => new XElement("size", s.ToString())))));
   
    xDoc.Save("Shoes.xml");
}

 

VB.NET

Public Sub Add() Implements ILinqToXml.Add
    Dim shoe = New Shoe() With {
        .Id = "456",
        .Manufacturer = "Reebok",
        .Model = "Pump",
        .Price = 34,
        .AvailableSize = New List(Of Integer)(New Integer() {38, 39, 48})
    }
 
    Dim xDoc = Me.LoadFile()
 
    xDoc.Element("Catalog").Add(New XElement("Shoe",
        New XAttribute("id", shoe.Id),
        New XElement("Manufacturer", shoe.Manufacturer),
        New XElement("Model", shoe.Model),
        New XElement("Price", shoe.Price.ToString("#,##0.00")),
        New XElement("AvailableSize",
                shoe.AvailableSize.Select(Function(s) New XElement("size", s.ToString())))))
   
    xDoc.Save("Shoes.xml")
End Sub

 

 Per quanto riguarda la modifica di un elemento invece, occorre prima di tutto individuarlo e poi utilizzare il metodo SetElementValue per poter settare il valore dell'elemento da variare.

C#

public void Update()
{
    //Aggiorno il prezzo dell'articolo con id NKAM
    var xDoc = this.LoadFile();
 
    var shoeToUpdate = xDoc.Descendants("Shoe").FirstOrDefault(f=>  f.Attribute("id").Value == "NKAM");
    if (shoeToUpdate != null)
    {
        shoeToUpdate.SetElementValue("Price", (45).ToString("#,##0.00"));
        xDoc.Save("Shoes.xml");
    }
}

 

VB.NET

Public Sub Update() Implements ILinqToXml.Update
    'Aggiorno il prezzo dell'articolo con id NKAM
    Dim xDoc = Me.LoadFile()
 
    Dim shoeToUpdate = xDoc.Descendants("Shoe").FirstOrDefault(Function(f) f.Attribute("id").Value = "NKAM")
    If shoeToUpdate IsNot Nothing Then
        shoeToUpdate.SetElementValue("Price", (45).ToString("#,##0.00"))
        xDoc.Save("Shoes.xml")
    End If
End Sub

 

Per terminare, invece, vediamo come eliminare uno o più record.

C#

public void Delete()
{
    //Cancello l'articolo con id NKAM
    var xDoc = this.LoadFile();
 
    xDoc.Descendants("Shoe").Where(f => f.Attribute("id").Value == "NKAM").Remove();
    xDoc.Save("Shoes.xml");
}

 

VB.NET

Public Sub Delete() Implements ILinqToXml.Delete
    'Cancello l'articolo con id NKAM
    Dim xDoc = Me.LoadFile()
    xDoc.Descendants("Shoe").Where(Function(f) f.Attribute("id").Value = "NKAM").Remove()
    xDoc.Save("Shoes.xml")
End Sub

 

Da questi esempi si è potuto intuire e/o ammirare la flessibilità e la semplicità di utilizzo di questo pezzetto di LINQ dedicato ai file XML. Il fatto di sfruttare LINQ, che ormai è diventato di uso comune, consente di manipolare file con una facilità disarmante e con un buon risparmio a livello di codice rispetto ai vari oggetti del suo predecessore DOM.

Ricordarsi però che LINQ to XML lavora in memoria, per cui per grossi file il caricamento del file risulterà piuttosto lento rispetto ai classici oggetti DOM, mentre per effettuare le query poi risulteranno più veloci, L'oggetto XMLReader rimane sempre il più veloce, per cui, in caso  file di enormi dimensioni o nei casi in cui le performance sono fondamentali, quest'ultimo rimane sempre l'approccio migliore….

Ok ragazzi, per ora è tutto ci vediamo nei prossimi articoli….. 

 

Dedico questo articolo, al mio caro papà,
che dal mese scorso non c'è più.
Ciao Riccardo


Tags: Linq,,xml

 
x