Работаем с LINQ to XML

В первой статье в блоге .NET «Работаем с XML» в комментариях народ потребовал статьи LINQ to XML. Что же, попробуем раскрыть принципы работы этой новой технологии от Microsoft.

Создадим базу для ведения каталога аудиозаписей. База будет состоять из треков:

* Код
* Название
* Исполнитель
* Альбом
* Продолжительность
* Жанр

Мы научимся добавлять, редактировать, удалять и делать различные выборки из нашей базы.

Для начала создадим консольное приложение (я пишу свои проекты на C#, но суть в общем-то понятна будет всем) и подключим необходимое пространство имен

using System.Xml.Linq;

Создание файлов XML
Создадим XML файл нашей базы содержащий несколько тестовых записей уже при помощи LINQ:

//задаем путь к нашему рабочему файлу XML
string fileName = “base.xml”;

//счетчик для номера композиции
int trackId = 1;
//Создание вложенными конструкторами.
XDocument doc = new XDocument(
new XElement(“library”,
new XElement(“track”,
new XAttribute(“id”, trackId++),
new XAttribute(“genre”, “Rap”),
new XAttribute(“time”, “3:24″),
new XElement(“name”, “Who We Be RMX (feat. 2Pac)”),
new XElement(“artist”, “DMX”),
new XElement(“album”, “The Dogz Mixtape: Who’s Next?!”)),
new XElement(“track”,
new XAttribute(“id”, trackId++),
new XAttribute(“genre”, “Rap”),
new XAttribute(“time”, “5:06″),
new XElement(“name”, “Angel (ft. Regina Bell)”),
new XElement(“artist”, “DMX”),
new XElement(“album”, “…And Then There Was X”)),
new XElement(“track”,
new XAttribute(“id”, trackId++),
new XAttribute(“genre”, “Break Beat”),
new XAttribute(“time”, “6:16″),
new XElement(“name”, “Dreaming Your Dreams”),
new XElement(“artist”, “Hybrid”),
new XElement(“album”, “Wide Angle”)),
new XElement(“track”,
new XAttribute(“id”, trackId++),
new XAttribute(“genre”, “Break Beat”),
new XAttribute(“time”, “9:38″),
new XElement(“name”, “Finished Symphony”),
new XElement(“artist”, “Hybrid”),
new XElement(“album”, “Wide Angle”))));
//сохраняем наш документ
doc.Save(fileName);

Теперь в папке с нашей программой после запуска появится XML файл следующего содержания:

Who We Be RMX (feat. 2Pac)
DMX
The Dogz Mixtape: Who’s Next?!

Angel (ft. Regina Bell)
DMX
…And Then There Was X

Dreaming Your Dreams
Hybrid
Wide Angle

Finished Symphony
Hybrid
Wide Angle

Для создания подобного файла средствами XmlDocument кода понадобилось где-то раза в 2 больше. В коде выше мы воспользовались конструктором класса XDocument, который принимает в качестве параметра перечень дочерних элементов, которыми мы изначально хотим инициализировать документ. Используемый конструктор XElement принимает в качестве параметра имя элемента, который мы создаем, а так же перечень инициализирующих элементов. Удобно то, что мы в этих элементах можем задавать как новые XElement, так и XAttribute. Последние отрендретятся в наш файл как атрибуты самостоятельно. Если вам не нравится использоваться такую вложенность конструкторов и вы считаете такой код громоздким, то можно переписать в более традиционный вариант. Код ниже даст на выходе аналогичный XML файл:

XDocument doc = new XDocument();
XElement library = new XElement(“library”);
doc.Add(library);

//создаем элемент “track”
XElement track = new XElement(“track”);
//добавляем необходимые атрибуты
track.Add(new XAttribute(“id”, 1));
track.Add(new XAttribute(“genre”, “Rap”));
track.Add(new XAttribute(“time”, “3:24″));

//создаем элемент “name”
XElement name = new XElement(“name”);
name.Value = “Who We Be RMX (feat. 2Pac)”;
track.Add(name);

//создаем элемент “artist”
XElement artist = new XElement(“artist”);
artist.Value = “DMX”;
track.Add(artist);

//Для разнообразия распарсим элемент “album”
string albumData = “The Dogz Mixtape: Who’s Next?!”;
XElement album = XElement.Parse(albumData);
track.Add(album);
doc.Root.Add(track);

/*
*остальные элементы добавляем по аналогии
*/

//сохраняем наш документ
doc.Save(fileName);

Естественно выбирать необходимый способ нужно по ситуации.

Чтение данных из файла
Сейчас попробуем просто прочитать данные из уже полученного файла и вывести их в удобном для восприятия виде в консоль:

//задаем путь к нашему рабочему файлу XML
string fileName = “base.xml”;
//читаем данные из файла
XDocument doc = XDocument.Load(fileName);
//проходим по каждому элементу в найшей library
//(этот элемент сразу доступен через свойство doc.Root)
foreach (XElement el in doc.Root.Elements())
{
//Выводим имя элемента и значение аттрибута id
Console.WriteLine(“{0} {1}”, el.Name, el.Attribute(“id”).Value);
Console.WriteLine(” Attributes:”);
//выводим в цикле все аттрибуты, заодно смотрим как они себя преобразуют в строку
foreach (XAttribute attr in el.Attributes())
Console.WriteLine(” {0}”, attr);
Console.WriteLine(” Elements:”);
//выводим в цикле названия всех дочерних элементов и их значения
foreach (XElement element in el.Elements())
Console.WriteLine(” {0}: {1}”, element.Name, element.Value);
}

Здесь в коде, думаю, ничего сложного нету и приведены комментарии. После запуска нашей программый в консоли отобразится следующий результат:

track 1
Attributes:
id=”1″
genre=”Rap”
time=”3:24″
Elements:
name: Who We Be RMX (feat. 2Pac)
artist: DMX
album: The Dogz Mixtape: Who’s Next?!
track 2
Attributes:
id=”2″
genre=”Rap”
time=”5:06″
Elements:
name: Angel (ft. Regina Bell)
artist: DMX
album: …And Then There Was X
track 3
Attributes:
id=”3″
genre=”Break Beat”
time=”6:16″
Elements:
name: Dreaming Your Dreams
artist: Hybrid
album: Wide Angle
track 4
Attributes:
id=”4″
genre=”Break Beat”
time=”9:38″
Elements:
name: Finished Symphony
artist: Hybrid
album: Wide Angle

Изменение данных
Попробуем пройтись по всем узлам library и увеличить аттрибут Id элемента track на 1.
(дальше писать объявление пути к файлу и результат вывода в консоль я приводить не буду, чтобы не перегружать лишней информацией статью, все компилировал, все работает:) ):

//Получаем первый дочерний узел из library
XNode node = doc.Root.FirstNode;
while (node != null)
{
//проверяем, что текущий узел – это элемент
if (node.NodeType == System.Xml.XmlNodeType.Element)
{
XElement el = (XElement)node;
//получаем значение аттрибута id и преобразуем его в Int32
int id = Int32.Parse(el.Attribute(“id”).Value);
//увеличиваем счетчик на единицу и присваиваем значение обратно
id++;
el.Attribute(“id”).Value = id.ToString();
}
//переходим к следующему узлу
node = node.NextNode;
}
doc.Save(fileName);

Теперь попробуем это сделать более правильным способом для наших задач:

foreach (XElement el in doc.Root.Elements(“track”))
{
int id = Int32.Parse(el.Attribute(“id”).Value);
el.SetAttributeValue(“id”, –id);
}
doc.Save(fileName);

Как видим – этот способ нам подошел больше.

Добавление новой записи
Добавим новый трек в нашу библиотеку, а заодно вычислим средствами LINQ следующий уникальный Id для трека:

int maxId = doc.Root.Elements(“track”).Max(t => Int32.Parse(t.Attribute(“id”).Value));
XElement track = new XElement(“track”,
new XAttribute(“id”, ++maxId),
new XAttribute(“genre”, “Break Beat”),
new XAttribute(“time”, “5:35″),
new XElement(“name”, “Higher Than A Skyscraper”),
new XElement(“artist”, “Hybrid”),
new XElement(“album”, “Morning Sci-Fi”));
doc.Root.Add(track);
doc.Save(fileName);

Вот таким подним запросом ко всем элементам вычисляется максимальное значение аттрибута id у треков. При добавлении полученное максимальное значение инкрементируем. Само же добавление элемента сводится к вызову метода Add. Обратите внимание, что добавляем элементы в Root, так как иначе нарушим структуру XML документа, объявив там 2 корневых элемента. Так же не забывайте сохранять ваш документ на диск, так как до момента сохранения никакие изменения в нашем XDocument не отразятся в XML файле.

Удаление элементов
Попробуем удалить все элементы исполнителя DMX:

IEnumerable tracks = doc.Root.Descendants(“track”).Where(
t => t.Element(“artist”).Value == “DMX”).ToList();
foreach (XElement t in tracks)
t.Remove();

В этом примере мы вначале выбрали все треки у который дочерний элемент artst удовлетворяет критерии, а потом в цикле удалили эти элементы. Важен вызов в конце выборки ToList(). Этим самым мы фиксируем в отдельном участке памяти все элементы, которые хотим удалить. Если же мы надумаем удалять из набора записей, по которому проходим непосредственно в цикле, мы получим удаление первого элемента и последующий NullReferenceException. Так что важно помнить об этом. По совету посмотреть профиль XaocCPS удалять можно и более простым способом:

IEnumerable tracks = doc.Root.Descendants(“track”).Where(
t => t.Element(“artist”).Value == “DMX”);
tracks.Remove();

В этом случае приводить к списку наш полученный результат вызовом функции ToList() не нужно. Почему этот способ не использовал изначально описал в комментарии :)

Немного дополнительных запросов к нашей базе треков
Отсортируем треки по продолжительности в обратном порядке:

IEnumerable tracks = from t in doc.Root.Elements(“track”)
let time = DateTime.Parse(t.Attribute(“time”).Value)
orderby time descending
select t;
foreach (XElement t in tracks)
Console.WriteLine(“{0} – {1}”, t.Attribute(“time”).Value, t.Element(“name”).Value);

Отсортируем элементы по жанру, исполнителю, названию альбома, названию трека:

IEnumerable tracks = from t in doc.Root.Elements(“track”)
orderby t.Attribute(“genre”).Value,
t.Element(“artist”).Value,
t.Element(“album”).Value,
t.Element(“name”).Value
select t;
foreach (XElement t in tracks)
{
Console.WriteLine(“{0} – {1} – {2} – {3}”, t.Attribute(“genre”).Value,
t.Element(“artist”).Value,
t.Element(“album”).Value,
t.Element(“name”).Value);
}

Простенький запрос, выводящий количество треков в каждом альбоме:

var albumGroups = doc.Root.Elements(“track”).GroupBy(t => t.Element(“album”).Value);
foreach (IGrouping a in albumGroups)
Console.WriteLine(“{0} – {1}”, a.Key, a.Count());

Выводы
После того как вы освоили пространство имен System.Xml для работы с XML на более низком уровне, смело переходите на использование System.Xml.Linq, надеюсь, написанная статья поможет это сделать быстрее, ведь не так страшен черт, как его рисуют. Как видно из примеров выше – многие вещи делать значительно проще, количество строк кода сокращается. Это дает нам очевидные преимущется, начиная со скорости разработки, заканчивая более легким сопровождением кода, написанного ранее.
Несмотря на сложности, организация практического взаимодействия инновационна. Организация практического взаимодействия спорадически детерминирует направленный маркетинг, работая над проектом. Продуктовый ассортимент, анализируя результаты рекламной кампании, основан на тщательном анализе. Побочный PR-эффект, как принято считать, притягивает департамент маркетинга и продаж, повышая конкуренцию. Стимулирование сбыта индуцирует медиаплан, осознав маркетинг как часть производства. Привлечение аудитории, не меняя концепции, изложенной выше, изменяет инвестиционный продукт, оптимизируя бюджеты.
Dodex 2012 - 2020
Электронная почта: contact@dodex.org
Skype: dodexorg
Twitter: @dodexorg