devxlogo

XLinq Part 1: Classes and Methods

XLinq Part 1: Classes and Methods

early every .NET application needs to handle data that may reside in a wide range of data sources, from relational databases, to XML, to in-memory or disk-persisted objects. Although .NET Framework 2.0 exposes a number of classes to handle these types of data, it lacks a unified approach to handling data from disparate data sources. In other words, until now, you needed to learn a multitude of technologies such as ADO.NET, SQL, XML DOM, XML serialization, etc., to be able to read and update these various data sources. To solve this problem, Microsoft has released a set of technologies called Language-integrated Query (LINQ) that is specifically geared towards making data access consistent?regardless of the back-end data store.

Key Components of LINQ
At this early stage, the LINQ family consists of three technologies, although Microsoft or third-party developers may add more in the future:

  • LINQ is a general purpose API that provides the core foundation for a consistent query experience.
  • DLinq (also known as LINQ to SQL) is a LINQ extension geared mainly towards relational databases.
  • XLinq (also known as LINQ to XML) is a LINQ extension for querying XML documents, as well as creating or transforming XML.

This article focuses primarily on XLinq, which provides an in-memory XML programming API for handling and processing XML data.

What You Need
Visual Studio 2005 Professional RTM and the LINQ May 2006 Community Technology Preview.

XLinq takes advantage of the standard query operators (where, select, order by, group by) supplied by LINQ and adds query extensions specific to XML. XLinq combines the query and transformation power of XQuery and XPath with the .NET Framework to provide a consistent query experience. Compared to the XML DOM (the traditional method of querying XML documents), the XLinq is a newly designed, lightweight API that provides significant improvements.

?
Figure 1. XLinq Class Hierarchy: The figure shows the classes and relationships in the System.Xml.XLinq namespace.

XLinq Class Hierarchy
You’ll find the XLinq-related classes in the System.Xml.XLinq namespace as shown in Figure 1.

Of the classes shown in Figure 1, the XNode and XContainer classes (shown in different color) are abstract. The XNode class is the base for element nodes, and provides a Parent method and methods such as AddBeforeThis, AddAfterThis, and Remove for updates in the imperative style. For IO, it provides methods for reading (ReadFrom) and writing (WriteTo).

Although the XElement class is bottom-most in the class hierarchy, it is the fundamental class. As the name suggests, it represents an XML element and allows you to perform the following operations:

  • Create elements with a specified element name
  • Change the element’s contents
  • Add, change, or delete child elements
  • Add attributes to the element
  • Save the element as an XML fragment
  • Extract the contents in text form

Compared to the XML DOM, XLinq is much more flexible because it does not require you to always have a document object to be able to work with XML. Therefore, you can work directly with nodes and modify them as content of the document without having to start from a root XmlDocument object. This is a very powerful and flexible feature that you can use to compose larger trees and XML documents from tree fragments. Now that you have an overview of the XLinq’s capabilities, the next few sections will examine the reading and writing features of XLinq before discussing the query capabilities.

Creating an XML Tree
XLinq provides a simplified yet powerful approach to creating XML elements. Using this approach, you can create all or part of your XML tree using a single line of code. This is referred to as functional construction. The following code constructs an XML block using this technique inside the Click event of a command button.

   private void btnCreateXML_Click(object     sender, EventArgs e)   {     XDocument bookStoreXml =      new XDocument(       new XDeclaration("1.0", "utf-8",         "yes"),       new XComment("Bookstore XML        Example"),                         new XElement("bookstore",        new XElement("genre",         new XAttribute("name",           "Fiction"),         new XElement("book",            new XAttribute("ISBN",            "10-861003-324"),                    new XAttribute("Title",            "A Tale of Two Cities"),          new XAttribute("Price",            "19.99"),                                       new XElement("chapter",            "Abstract...",           new XAttribute("num", "1"),           new XAttribute("name",             "Introduction")           ),                                  new XElement("chapter",            "Abstract...",           new XAttribute("num", "2"),           new XAttribute("name", "Body")              )          )         )                            )      );                              MessageBox.Show(bookStoreXml.Xml);    }

In the preceding code, a root XML element called acts as a container for all the child elements. To the constructor of the XElement, you can also supply all the child elements. This is made possible by the following constructor of the XElement that accepts a variable number of arguments through the params keyword.

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

Using this constructor, you can pass in a number of XElement objects that make up the content of the XML element. The supplied XElement objects are included as children of the root element. The XML output produced by the preceding example is:

                            Abstract...                    Abstract...                          

As mentioned before, the XLinq API makes working with XML elements and attributes much easier by constructing them independently of a container document. As a result, nodes are truly first-class values that can be freely passed to and returned from functions. Although the object model does provide the notion of an XmlDocument (represented by the XDocument object in XLinq), it does not require one in any shape or form to construct individual elements or attributes.

The above example used an XDocument object to insert the processing instruction at the top of the XML block. If you are constructing fragments of XML tree, you can simply use the XElement to accomplish that without going through an XDocument object.

Adding Namespaces to XML Elements
The previous example demonstrated the power of the functional construction approach, which is both nice and very extensible, because you can easily build on it to add namespaces to the XML elements. For example, you can add the namespace http://www.bookstorexml.com to the bookstore element using the following code snippet.

   XNamespace nameSpace =     "http://www.bookstorexml.com";   XDocument bookStoreXml =   new XDocument(       new XDeclaration("1.0", "utf-8",        "yes"),       new XComment("Bookstore XML         Example"),                         new XElement(nameSpace +         "bookstore",         ----         ----       )     );

All you need to do is to append the XNamespace element to the name of the XML element when you instantiate the XElement object. This results in the following output:

          ----    ----    ----   

Saving XML
After reading in your XML or creating some from scratch, and then manipulating it in various ways, you will probably want to output your XML. To accomplish this, you can use one of the overloaded XElement or XDocument Save() methods to output content in a variety of ways. You can save to a file, a TextWriter, or an XmlWriter. For example, to save the XDocument contents to a file named Bookstore.xml, you can write:

   private void btnSaveXML_Click(    object sender, EventArgs e)   {    XDocument bookStoreXml =   new XDocument(      new XDeclaration("1.0", "utf-8",       "yes"),      new XComment("Bookstore XML        Example"),                        new XElement("bookstore",       ----       ----      )    );     bookStoreXml.Save   (@"C:TempBookstore.xml");                  MessageBox.Show("Successfully saved");   }   

Loading XML
You can load existing XML into an XElement object so that you can read it or manipulate it. Using the following two methods of the XElement object you can load XML data from a multitude of input sources such as a file, an XmlReader, a TextReader or a string.

  • Load?Allows you to load XML from a file, or from a XmlReader, or a TextReader object
  • Parse?Allows you to load XML from a string

As an example, the following code shows how to load XML from a file named bookstore.xml.

?
Figure 2. Bookstore XML Output: Here’s a MessageBox showing the bookstore.xml file content.
   private void btnLoadXmlFromFile_Click    (object sender, EventArgs e)   {    XElement bookStoreXml =      XElement.Load   (@"C:TempBookstore.xml");    MessageBox.Show(bookStoreXml.Xml);   }

Assuming that you have the bookstore.xml file loaded with XML data from the previous section, the Xml property of the XElement object produces the output in Figure 2.

Querying XML
XLinq provides a number of methods for querying the XElement and XDocument objects. These methods are defined in the abstract XContainer class. The key methods are shown in Table 1.

Table 1: Query Methods: The table shows common methods for querying the content of XElement and XDocument objects.

Method Description
Ancestors Returns the ancestor XML elements in the form of an IEnumerable list.
DescendantNodes Returns the descendant nodes of the XContainer.
Descendants Returns the descendant XML elements in the form of an IEnumerable list.
Element Returns the child element with the specified name.
Elements Returns the child elements of the current node. You can also supply the name of the XML element to return the child nodes that match the supplied name.
Nodes Returns the contents of the current node.

In addition to the above methods, the XContainer also exposes properties such as FirstNode, NextNode, and LastNode that allow you to get references to specific nodes contained in the current node. For example, here’s how you could get a reference to the element using the Element method.

   XElement bookStoreXml =  XElement.Load     (@"C:TempBookstore.xml");   XElement bookXml =      bookStoreXml.Element    ("genre").Element("book");   

Once you have a reference to the element, you can easily reference any of its child nodes, including the elements using the Elements method.

   string output =  "";   foreach (XElement element in      bookXml.Elements("chapter"))   {     output += element;      }   MessageBox.Show(output);

The preceding code loops through the collection of elements and adds them to a string variable named output. Finally, the last line displays the contents of the string variable in a MessageBox as shown in Figure 3.

?
Figure 3. Looping Through Elements: Here’s the output of the code that loops through the element’s children, selecting the elements.

In addition to retrieving elements, you can also leverage the XElement class’ Attribute and Attributes methods to retrieve attributes. These methods work similarly to the XContainer’s Element and Elements methods.

Updating XML
Updating an XML element is simple and straightforward. All you need to do is navigate to the XElement whose contents you want to replace, and then invoke the ReplaceContent() method, passing in the new value for that particular element. For example, if you wanted to change the content of the chapter element, you could do the following:

   chapterElement.ReplaceContent     ("Modified abstractÂ….");

You can also update an XML subtree using ReplaceContent(). For example, to update the contents of the element, you could do the following:

   XElement bookStoreXml =  XElement.Load    (@"C:TempBookstore.xml");                           bookStoreXml.Element("genre").Element    ("book").ReplaceContent(   new XElement("chapter",     "Modified Abstract...",      new XAttribute("num", "6"),      new XAttribute("name", "Header")      ),                           new XElement("chapter",     "Modified Abstract...",   new XAttribute("num", "7"),   new XAttribute("name", "Footer")         )                        );    bookStoreXml.Save    (@"C:TempBookstore.xml");

The preceding code updates the entire XML subtree with new contents using the ReplaceContent() method. A similar method called SetElement() lets you update elements that contain simple content.

Deleting XML
The XElement class exposes the methods shown in Table 2 for deleting content from an XML element.

Table 2: The XElement class exposes several methods for deleting content from an XML element.

Method Description
Remove Removes this node from the XML tree.
RemoveAll Removes contents and attributes from the current element.
RemoveAnnotation Removes an annotation from the current node.
RemoveAttributes Removes all the attributes of the current node.
RemoveContent Removes all child nodes of the current node without removing attributes.

Now that you’ve had a brief look at the methods, here are a couple of examples.

To delete XML, navigate to the content you want to delete and call Remove(). For example, if you want to delete the element under the element:

   bookStoreXml.Element("genre").Remove();

Remove() also accepts objects that support the IEnumerable interface, so for example, you can delete all the chapters under the element using:

   book.Elements("chapter").Remove();

You can remove all the content from an XElement with the RemoveContent() method.

   bookStoreXml.Element("genre").    Element("book").RemoveContent();

XLinq Support in VB 9
The next version of VB (VB 9) takes XLinq support to the next level by allowing you to embed XML literals directly inside Visual Basic. Within the XML literals you can leave placeholders for attributes, attribute names, or attribute values, for element names by using (expression), or for child elements using the ASP.NET style syntax , or for blocks. The following example shows how to accomplish this:

   Dim text As String = "Abstract..."   Dim chapterXml = _                                                                                      'Construct a new element based on the 'existing ones    Dim bookStoreXml = _                                                                                                                                                                                              MessageBox.Show(bookStoreXml.Xml)

Note that the syntax lets you retrieve values from the text and chapterXml variables and place them inside the and elements respectively.

The Visual Basic compiler takes XML literals and translates them into constructor calls to the underlying XLinq API. As a result, you can freely pass XML produced by Visual Basic to any other component that accepts XLinq values. Similarly, Visual Basic code can accept XLinq XML produced by external components.

This article has only scratched the surface XLinq’s possibilities. Future installments of this article series will go into more detail about XLinq’s capabilities, including XML transformation and XLinq’s integration with DLinq for accessing data stored in relational databases.

devxblackblue

About Our Editorial Process

At DevX, we’re dedicated to tech entrepreneurship. Our team closely follows industry shifts, new products, AI breakthroughs, technology trends, and funding announcements. Articles undergo thorough editing to ensure accuracy and clarity, reflecting DevX’s style and supporting entrepreneurs in the tech sphere.

See our full editorial policy.

About Our Journalist