LINQ to XML cz. 4, Transformacje i serializacja
Drugim ważnym zastosowaniem Linq to Xml jest serializacja dokumentów Xmlowych. Serializacja i deserializacja to procesy umożliwiające konwersje dokumentów Xmlowych odpowiednio z i do postaci obiektowej. Tak jak w przypadku transformacji Linq to Xml nie stara się konkurować z istniejącym na platformie .NET technologiami do serializacji (XmlSerializer, DataContractSerializer) ale jest bardzo poręczny w przypadkach gdy trzeba dokonać serializacji małych obiektów bezpośrednio w kodzie.
Zacznijmy od deserializacji - elementy "wylot" i "przylot" można przedstawić w sposób obiektowy za pomocą następującej klasy (warto zauważyć w jaki sposób zdefiniowane są pola - wykorzystują one wprowadzone w C# 3.0 tzw. "automatic properties". Dzięki nim nie trzeba definiować prywatnych zmiennych w klasie do przechowywania wartości pola - kompilator zrobi to za nas):
public class Przelot {
public string KodLotniska { get; set; }
public DateTime Data { get; set; }
public string NazwaLotniska { get; set; }
}Kod realizujący taką serializację jest dzięki Linq to Xml stosunkowo prosty. Z dokumentu Xmlowego wybieramy elementy "wylot" i "przylot" i dla każdego takiego elementu tworzymy obiekt klasy Przelot. Oto przykład:
IEnumerable<Przelot> przeloty =
from przelot in xDoc.Elements("wycieczka").Elements("segment").Elements()
select new Przelot() {
Data = (DateTime)przelot.Attribute("data"),
KodLotniska = (string)przelot.Attribute("kod-lotniska"),
NazwaLotniska = (string)przelot
};
W powyższym fragmencie jest zastosowany nowy sposób inicjalizacji obiektu po jego stworzeniu - mimo, że klasa Przelot nie posiada żadnego poza domyślnym konstruktora możemy w prosty sposób zainicjalizować pola stworzonego obiektu dzięki nowej składni wprowadzonej w C# 3.0. Ten nowy, poręczny sposób inicjalizacji obiektów nazywa się "object initializers".
Inną nowością w C# 3.0 są klasy anonimowe. Pozwalają one na stworzenie obiektów "ad hoc" bez konieczności definiowania klas. Za pomocą składni podobnej do "object initizalizers" tworzymy obiekt nie nazwany, w którym tworzymy właściwości dynamicznie przez przypisanie wartości danej właściwości. Aby odwołać się później do takiego obiektu deklarujemy zmienną za pomocą słowa kluczowego "var" (pozwalając w ten sposób kompilatorowi określić rzeczywisty typ zmiennej ponieważ sami go tak naprawdę nie znamy) i odwołujemy się do obiektu za pomocą nazw właściwości użytych podczas tworzenia obiektu. Poniższy fragment przedstawia zastosowanie klas anonimowych do deserializacji dokumentów Xmlowych za pomocą Linq to Xml:
var przeloty =
from przelot in xDoc.Elements("wycieczka").Elements("segment").Elements()
select new {
Date = (DateTime)przelot.Attribute("data"),
AirportCode = (string)przelot.Attribute("kod-lotniska"),
AirportName = (string)przelot
};
foreach(var przelot in przeloty) {
Console.WriteLine(string.Format("{0} ({1}) {2}",
przelot.AirportName, przelot.AirportCode, przelot.Date));
}
Jak widać z powyższych przykładów, że deserializacja dokumentów Xmlowych do obiektów za pomocą Linq to Xml nie jest skomplikawana. A jak jest z serializacją? Najlepiej chyba pokazać to na przykładzie. Załóżmy, że mamy tablicę zawierającą obiekty typu Przelot (definicja klasy Przelot taka jak w jednym z wcześniejszych przykładów). Zdefiniujmy ją w następujący sposób:
var przeloty = new[] {
new {
Wylot = new Przelot() {
KodLotniska = "SEA",
Data = DateTime.Parse("2008-10-09T15:25Z"),
NazwaLotniska = "Seattle-Tacoma International"
},
Przylot = new Przelot() {
KodLotniska = "LHR",
Data = DateTime.Parse("2008-10-10T02:20Z"),
NazwaLotniska = "London Heathrow"
},
},
new {
Wylot = new Przelot() {
KodLotniska = "LHR",
Data = DateTime.Parse("2008-10-10T05:35Z"),
NazwaLotniska = "London Heathrow"
},
Przylot = new Przelot() {
KodLotniska = "WRO",
Data = DateTime.Parse("2008-10-10T10:05Z"),
NazwaLotniska = "Wroclaw Strachowice"
}
}
};Tablica zdefiniowana jest za pomocą "wodotrysków" pochodzących z C# 3.0. Mianowicie jest to tablica obiektów anonimowych, z których każdy zawiera informacje o wylocie i przylocie będącymi instancjami klasy Przelot. Instancje klasy Przelot inicjalizowane są za pomocą funkcjonalności "object initializers". Zadanie polega na zamianie powyższej tablicy na dokument Xmlowy taki jak ten używany w przykładach w naszym cyklu artykułów. Voila - oto kod:
XDocument xDoc = new XDocument(
new XDeclaration("1.0", string.Empty, string.Empty),
new XElement("wycieczka",
from przelot in przeloty
select new XElement("segment",
new XElement("wylot",
new XAttribute("kod-lotniska", przelot.Wylot.KodLotniska),
new XAttribute("data", przelot.Wylot.Data),
new XText(przelot.Wylot.NazwaLotniska)),
new XElement("przylot",
new XAttribute("kod-lotniska", przelot.Przylot.KodLotniska),
new XAttribute("data", przelot.Przylot.Data),
new XText(przelot.Przylot.NazwaLotniska)))));
Kod może wydawać się odrobinę znajomy... Szczególnie jak się spojrzy na kod z pierwszego odcinka cyklu oraz na przykładowy kod pokazujący w jaki sposób dokonać transformacji dokumentu Xmlowego za pomocą Linq to Xml. Podobieństwo do kodu z pierwszego odcinku cyklu nie powinno dziwić - w końcu budujemy praktycznie ten sam dokument z tą tylko różnicą, że zamiast wstawiać wszystkie wartości ręcznie chcemy je pobrać z tablicy. Musimy zatem iterować po tej tablicy do czego używamy technologii Linq co z kolei przypomina kod z tego artykułu. Uważni jednak zauważą jedną dość istotną różnicę. Mianowicie przykładowa transformacja przedstawiona na początku artykułu iterowała po elementach z przestrzeni nazw System.Xml.Linq. Natomiast w tym przypadku iterujemy po "normalnej" tablicy obiektów. Jak to jest możliwe? Otóż w tamym przykładzie używaliśmy Linq to Xml podczas gdy w tym przykładzie została użyta technologia Linq to... Objects. Tak oto Linq spotkał Linqa...

