Galin Iliev

Software Architecture & Development

LINQ to XML

LINQ to XML предоставя нов програмен модел за четене, записване и конструиране на XML. Този модел е много по-удобен от DOM API (на XmlDocument), което се използва до момента, а и използва много по-малко памет. Освен това е много по-лесен за използване от XmlReader/XmlWriter. За да се постигне това улеснение са създадени нов набор от класове за работа с XML в пространството от имена System.Xml.Linq в асемблито System.Xml.Linq.dll.

Четене и обхождане на XML

Нека да разгледаме следният xml документ:

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

<Products>

  <Product ProductName="Alice Mutton">

    <ProductID>17</ProductID>

    <UnitPrice>39.0000</UnitPrice>

    <UnitsInStock>0</UnitsInStock>

  </Product>

  <Product ProductName="Aniseed Syrup">

    <ProductID>3</ProductID>

    <UnitPrice>10.0000</UnitPrice>

    <UnitsInStock>13</UnitsInStock>

  </Product>

</Products>

Този фрагмент съдържа основен (root) елемент <Products>, който съдържа два поделемента от тип <Product>. Данните от елементите <Product> са съхранени по два начина – като атрибути и като поделементи. С помощта на следния израз можем да прочетем документа и да го покажем на в конзолата:

XElement xml = XElement.Load(@"D:\LINQTutorial\Products.xml");

 

var products = from p in xml.Descendants("Product")

               orderby p.Element("ProductID").Value descending

               select new {

                   ProductName = p.Attribute("ProductName").Value,

                   ProductID = p.Element("ProductID").Value,

                   UnitPrice = p.Element("UnitPrice").Value,

                   UnitsInStock = p.Element("UnitsInStock").Value

               };

 

foreach (var item in products) {

    Console.WriteLine("{0}\t{1}\t{2}\t{3}", item.ProductID, item.ProductName, item.UnitsInStock, item.UnitPrice);

}

На първият ред създаваме инстанция от тип XElement, зареждайки XML документа от файловата система. Следва по-интересната част – самата LINQ заявка.

От първият ред на заявката определяме основната променлива р (отново от тип XElement), която ще представлява всеки елемент върнат от израза xml.Descendants("Product"). Метода връща всички поделементи от тип <Product>. За да извлечем данните използваме два метода – XElement.Element() и  XElement.Attribute(), съответно, за да извлечем данните съответно от поделемените и от атрибутите. Забележете, че тези два метода отново връщат резултат от тип XElement и за да извлечем данните трябва да използваме свойството Value.

Тук обаче се крие опасност – тъй като подаваме имената на елементите и атрибутите като текст има възможност за грешка, а е възможно също така даден елемент да не съдържа всички поделементи и атрибути. В този случай XElement.Element() и  XElement.Attribute() ще няма да намерят търсеният елемент/атрибут и ще върнат null. Когато се опитаме да вземем стойността на свойството Value ще получим NullReferenceException. За да избегнем подобна ситуация може да използваме помощен метод:

var products = from p in xml.Descendants("Product")

               orderby p.Element("ProductID").Value descending

               select new {

                   ProductName = GetXElementValue(p.Attribute("ProductName")),

                   ProductID = GetXElementValue(p.Element("ProductID")),

                   UnitPrice = GetXElementValue(p.Element("UnitPrice")),

                   UnitsInStock = GetXElementValue(p.Element("UnitsInStock")),

               };

 

private string GetXElementValue(XElement el) {

    if (null != el) return el.Value;

    return string.Empty;

}

По този начин се подсигуряваме, че няма да възникне изключение.

Създаване на XML

Освен прочитането и обхождането на XML документ, можем много лесно да генерираме такъв. Понеже класът XElement е много гъвкав позволява генерирането на цял документ с един израз:

XElement e = new XElement("Products",

                 new XElement("Product",

                    new XAttribute("ProductName", "Product1"),

                    new XElement("ProductID", 1),

                    new XElement("UnitPrice", 11.5),

                    new XElement("UnitsInStock", 12)),

                 new XElement("Product",

                    new XAttribute("ProductName", "Product2"),

                    new XElement("ProductID", 2),

                    new XElement("UnitPrice", 2.45),

                    new XElement("UnitsInStock", 89))

                  );

Въпреки, че класът XElement е нов горната конструкция не е нова – в нея използваме комбинация от два конструктура на XElement, за да постигнем целта:

public XElement(XName name, params object[] content)

public XElement(XName name, object content)

Тези два конструктура ни позволяват да комбинираме LINQ to XML и LINQ to SQL, за да експортираме данни към XML. В следващият пример комбинираме двата конструктура на XElement с зявка от LINQ to SQL, за да получим XML документ със същата структура, какъвто показахме в началото на тази точка:

NorthwindDataContext db = new NorthwindDataContext();

 

XElement xml = new XElement("Products",

        from p in db.Products

        orderby p.ProductName

        select new XElement("Product",

                  new XAttribute("ProductName", p.ProductName),

                  new XElement("ProductID", p.ProductID),

                  new XElement("UnitPrice", p.UnitPrice),

                  new XElement("UnitsInStock", p.UnitsInStock))

        );

 

xml.Save(@"D:\LINQTutorial\Products.xml");

В този пример вместо да подадем с код поредицата от параметри за конструктура, изискващ params object[] content, подаваме резултат от LINQ to SQL заявка, която връща инстанции на XElement.

Това показва силата на LINQ и неговите клонове. Със сигурност ако трябваше да изпълним тази задача с помощта на C# 2.0 кодът нямаше да е толкова малко и така подреден.

 

Следваща част: LINQ to DataSet

Content