šŸ“œ ā¬†ļø ā¬‡ļø

We work with LINQ to XML

In the first article in the .NET blog ā€œ Working with XML ā€ in the comments, people demanded LINQ to XML articles. Well, let's try to reveal the principles of this new technology from Microsoft.

Create a database for maintaining a catalog of audio recordings. The base will consist of tracks:

We will learn to add, edit, delete and make various selections from our database.

To begin with, we will create a console application (I am writing my own projects in C #, but the essence will be generally understood by everyone) and we will connect the necessary namespace
')
  using System.Xml.Linq; 


Creating XML Files



Create an XML file of our database containing several test records already using LINQ:

 // set the path to our working XML file
 string fileName = "base.xml";

 // counter for composition number
 int trackId = 1;
 // Create nested constructors.
 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"))));
 // save our document
 doc.Save (fileName); 


Now in the folder with our program after launch, the XML file will appear as follows:
  <? xml version = "1.0" encoding = "utf-8"?>
 <library>
   <track id = "1" genre = "Rap" time = "3:24">
     <name> Who We Be RMX (feat. 2Pac) </ name>
     <artist> DMX </ artist>
     <album> The Dogz Mixtape: Who's Next?! </ album>
   </ track>
   <track id = "2" genre = "Rap" time = "5:06">
     <Angel> Name (ft. Regina Bell) </ name>
     <artist> DMX </ artist>
     <album> ... And Then There Was X </ album>
   </ track>
   <track id = "3" genre = "Break Beat" time = "6:16">
     <name> Dreaming Your Dreams </ name>
     <artist> Hybrid </ artist>
     <album> Wide Angle </ album>
   </ track>
   <track id = "4" genre = "Break Beat" time = "9:38">
     <name> Finished Symphony </ name>
     <artist> Hybrid </ artist>
     <album> Wide Angle </ album>
   </ track>
 </ library> 


To create such a file using XmlDocument code, it took about 2 times more. In the code above, we used the XDocument class constructor, which takes as a parameter a list of child elements with which we initially want to initialize the document. The used XElement constructor takes as a parameter the name of the element that we create, as well as a list of initializing elements. Conveniently, we in these elements can set both new XElement and XAttribute. The latter will be rendered to our file as attributes on their own. If you do not like to use such nesting of constructors and you find such code cumbersome, then you can rewrite to a more traditional version. The code below will output a similar XML file:

  XDocument doc = new XDocument ();
 XElement library = new XElement ("library");
 doc.Add (library);

 // create the element "track"
 XElement track = new XElement ("track");
 // add necessary attributes
 track.Add (new XAttribute ("id", 1));
 track.Add (new XAttribute ("genre", "Rap"));
 track.Add (new XAttribute ("time", "3:24"));

 // create the element "name"
 XElement name = new XElement ("name");
 name.Value = "Who We Be RMX (feat. 2Pac)";
 track.Add (name);

 // create the element "artist"
 XElement artist = new XElement ("artist");
 artist.Value = "DMX";
 track.Add (artist);

 // For variety, parse element "album"
 string albumData = "<album> The Dogz Mixtape: Who's Next?! </ album>";
 XElement album = XElement.Parse (albumData);
 track.Add (album);
 doc.Root.Add (track);

 / *
 * add other elements by analogy
 * /

 // save our document
 doc.Save (fileName);


It is natural to choose the necessary method according to the situation.

Reading data from a file



Now let's try to just read the data from the already received file and output it in a convenient for perception view to the console:

  // set the path to our working XML file
 string fileName = "base.xml";
 // read data from file
 XDocument doc = XDocument.Load (fileName);
 // we pass on each element in the library
 // (this element is immediately available through the doc.Root property)
 foreach (XElement el in doc.Root.Elements ())
 {
	 // Display the name of the element and the value of the attribute id
	 Console.WriteLine ("{0} {1}", el.Name, el.Attribute ("id"). Value);
	 Console.WriteLine ("Attributes:");
	 // we loop out all the attributes, at the same time we see how they convert themselves into a string
	 foreach (XAttribute attr in el.Attributes ())
		 Console.WriteLine ("{0}", attr);
	 Console.WriteLine ("Elements:");
	 // in the loop we print the names of all the child elements and their values
	 foreach (XElement element in el.Elements ())
		 Console.WriteLine ("{0}: {1}", element.Name, element.Value);
 } 


Here in the code, I think, there is nothing complicated and comments are given. After starting our program, the following result will be displayed in the console:

  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 


Data change



Let's try to go through all the library nodes and increase the Id attribute of the track element by 1.
(I’m not going to write the output of the path to the file and output the output to the console, so as not to overload the article with extra information, compiled everything, everything works :)) :

  // Get the first child node from the library
 XNode node = doc.Root.FirstNode;
 while (node! = null)
 {
	 // check that the current node is an element
	 if (node.NodeType == System.Xml.XmlNodeType.Element)
	 {
		 XElement el = (XElement) node;
		 // get the value of the id attribute and convert it to Int32
		 int id = Int32.Parse (el.Attribute ("id"). Value);
		 // increment the counter by one and set the value back
		 id ++;
		 el.Attribute ("id"). Value = id.ToString ();
	 }
	 // go to the next node
	 node = node.NextNode;
 }
 doc.Save (fileName); 


Now we will try to do it in a more correct way for our tasks:

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


As you can see - this method came up to us more.

Add new record



Let's add a new track to our library, and at the same time we will calculate the following unique Id for the track using LINQ:

  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); 


This is the way to raise the request for all elements to calculate the maximum value of the id attribute of the tracks. When adding the resulting maximum value is incremented. The very same addition of an element is reduced to a call to the Add method. Please note that we add elements to Root, since otherwise we will break the structure of the XML document by declaring 2 root elements there. Also, do not forget to save your document to disk, because until the moment of saving, no changes in our XDocument will be reflected in the XML file.

Deleting items



Let's try to delete all elements of the DMX artist:

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


In this example, we first selected all the tracks for which the child element artst satisfies the criteria, and then in the loop deleted these elements. The challenge at the end of the ToList () sample is important. By this we fix in a separate section of memory all the elements that we want to delete. If we decide to remove from the recordset, which we are going through directly in the loop, we will get the removal of the first element and the subsequent NullReferenceException. So it is important to remember this.
On the advice of xaoccps, you can delete it in a simpler way:
  IEnumerable <XElement> tracks = doc.Root.Descendants ("track"). Where (
				 t => t.Element ("artist"). Value == "DMX");
 tracks.Remove (); 

In this case, it is not necessary to list our result by calling the ToList () function. Why this method is not used originally described in the comments :)


A few additional requests to our database of tracks



Sort the tracks by duration in reverse order:

  IEnumerable <XElement> 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); 


Sort items by genre, artist, album name, track title:

  IEnumerable <XElement> 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);
 } 


A simple query that displays the number of tracks in each album:

  var albumGroups = doc.Root.Elements ("track"). GroupBy (t => t.Element ("album"). Value);
 foreach (IGrouping <string, XElement> a in albumGroups)
	 Console.WriteLine ("{0} - {1}", a.Key, a.Count ()); 


findings


After you have mastered the System.Xml namespace for working with XML at a lower level, feel free to switch to using System.Xml.Linq, I hope the written article will help to do it faster, because the devil is not so bad as it is painted. As you can see from the examples above, many things are much easier to do, the number of lines of code is reduced. This gives us the obvious advantage, starting with the speed of development, ending with easier maintenance of the code written earlier.

Source: https://habr.com/ru/post/24673/


All Articles