LINQ to XML doesn't only provide for queries; it also provides for creating and modifying XML documents. Thanks to a feature called functional construction, you can create XML documents in an easier way than with the W3C DOM library.
We'll use functional construction to create an XML document from scratch, and then use other LINQ to XML features to manage it.
Finally, we'll look at a couple of examples that integrate LINQ to XML with LINQ to SQL to generate XML output from a database query.
If you are accustomed to using the W3C DOM library or the .NET Framework implementation of it, you'll find LINQ to XML very easy and more intuitive to use. The very simple code snippet that follows creates a new person element, followed by its elements:
XElement xml = new XElement("people", new XElement("person", new XElement("id", 1), new XElement("firstname", "Carl"), new XElement("lastname", "Lewis"), new XElement("idrole", 2)));
The functional construction model allows us to use the LINQ to XML classes in a "linked" way. You can create objects with a hierarchical structure that it is similar to the one used in an XML document. Moreover, it is a top-down approach to XML document creation that is more intuitive than the bottom-up approach of the W3C libraries. So the first element to create is the root, people:
XElement xml = new XElement("people",
To its constructor we can pass another XElement object (person) that will become the child element of people:
XElement xml = new XElement("people", new XElement("person",
We pass other XElement objects to the person constructor to define its children:
new XElement("person", new XElement("id", 1), new XElement("firstname", "Carl"), new XElement("lastname", "Lewis"), new XElement("idrole", 2)));
The XML document will look like this:
<people> <person> <id>1</id> <firstname>Carl</firstname> <lastname>Lewis</lastname> <idrole>2</idrole> </person> </people>
Functional construction is based on the XElement class's constructor, which accepts an array of param objects:
public XElement(XName name, params object[] contents)
Because this constructor accepts an array of generic object types, we can pass it any kind of information, such as the following:
An XAttribute object that will be used as an attribute for the current element
A string value that will be used as a value for the current element
A null value that will be ignored
An IEnumerable object that will be enumerated and its elements added recursively into the XML document
A value (variable, constant, property, or method call) to be used as value for the current element
An XComment object that will be added as child element
An XProcessingInstruction object that will generate a processing instruction as a child element
Using the XDeclaration Class
You can use an XDocument object to specify the XML declaration, as in Listing 3-16.
XDocument xml = new XDocument( new XDeclaration("1.0", "UTF-8", "yes"), new XElement("people", new XElement("idperson", new XAttribute("id", 1), new XAttribute("year", 2004), new XAttribute("salaryyear", "10000,0000")))); System.IO.StringWriter sw = new System.IO.StringWriter(); xml.Save(sw); Console.WriteLine(sw); |
|
Listing 3-16 also shows using XAttribute to add attribute values to the current element. Moreover, the Save method, provided by both the XDocument and XElement classes, saves the complete XML document into a StringWriter object (see "Loading and Saving XML" later in this chapter). Figure 3-8 shows the output of Listing 3-16.
Using the XNamespace Class to Create an XML Document
If you need to use XML namespace declarations, use the XNamespace class. Using it to create an XML document is similar to using it to query elements and attributes. We have to use the namespace variable in the XElement class constructor (or in the XAttribute class if we are going to specify an attribute), and add the string that represents the name of the element or the attribute.
Listing 3-17 shows how to produce the first three rows of the Hello_LINQ to XML.xml Microsoft Word XML file used during previous examples.
XNamespace w = "http://schemas.microsoft.com/office/word/2003/wordml"; XDocument word = new XDocument( new XDeclaration("1.0","utf-8", "yes"), new XProcessingInstruction("mso-application", "progid="Word.Document""), new XElement (w + "wordDocument", new XAttribute (XNamespace.Xmlns + "w", w. NamespaceName))); System.IO.StringWriter sw = new System.IO.StringWriter(); word.Save(sw); Console.WriteLine(sw); |
The bold code shows how to add a namespace and how to add an element and attribute associated with that namespace. The output of this code is as follows:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?> <?mso-application progid="Word.Document"?> <w:wordDocument xmlns:w="http://schemas.microsoft.com/office/word/2003/wordml" />
Transforming XML
A really cool feature of the XElement class constructor is that it can take an IEnumerable<XElement> collection as an argument. The constructor will iterate automatically through the collection and create child elements of the current element.
This kind of collection is usually produced by a LINQ query, so we could query an XML document and produce a new XML document with different characteristics. This process is called XML transformation, and it's usually done with the W3C's XSL Transformations (XSLT) language.
However, by using functional construction and providing an IEnumerable collection to the XElement constructor we can easily transform an XML document using LINQ to XML.
In Listing 3-18 some information within the People XML document is transformed into an HTML table.
XElement xml = XElement.Load(@"....People.xml"); XElement html = new XElement("HTML", new XElement("BODY", new XElement("TABLE", new XElement("TH", "ID"), new XElement("TH", "Full Name"), new XElement("TH", "Role"), from p in xml.Descendants("person") join r in xml.Descendants("role") on (int) p.Element("idrole") equals (int) r.Element("id") select new XElement("TR", new XElement("TD", p.Element("id").Value), new XElement("TD", p.Element("firstname").Value + " " + p.Element("lastname").Value), new XElement("TD", r.Element("roledescription").Value))))); html.Save(@"C:People.html"); |
The bold code in Listing 3-18 is the LINQ to XML query used to create the HTML table content. It joins person information in the XML document with role information. Then it selects the values that correspond to the HTML columns declared in the HTML header. The output is then saved into an HTML page using the Save method (see Figure 3-9).
LINQ to XML provides easy methods to load and save XML documents. We've already used Load and Save in some of the examples in this chapter. The Load method you've used accepts a string argument that specifies the path where the XML file is located. Similarly, the Save method accepts a string argument in which you specify the path to store the XML document. However, these methods have other interesting overloads that we we'll look at soon.
Also, some other methods deserve mention. For example, the Parse method provided by the XElement class allows developers to build an XML document starting from a string (see Listing 3-19).
string doc = @"<people> <!-- Person section --> <person> <id>1</id> <firstname>Carl</firstname> <lastname>Lewis</lastname> <idrole>1</idrole> </person> </people>"; XElement xml = XElement.Parse(doc); Console.WriteLine(xml); |
If you uses the .NET XmlReader and XmlWriter classes to read and write XML documents, you can pass these objects to the Load and Save methods. Listing 3-20 shows how to use XmlReader and XmlWriter with Load() and Save().
XmlReader reader = XmlReader.Create(@"....People.xml"); XDocument xml = XDocument.Load(reader); Console.WriteLine(xml); XElement idperson = xml.Descendants("idperson").Last(); idperson.Add(new XElement("idperson", new XAttribute("id", 1), new XAttribute("year", 2006), new XAttribute("salaryyear", "160000,0000"))); StringWriter sw = new StringWriter(); XmlWriter w = XmlWriter.Create(sw); xml.Save(w); w.Close(); Console.WriteLine(sw.ToString()); |
The static Create method creates an XmlReader object and reads the XML document from the path specified as the argument:
XmlReader reader = XmlReader.Create(@"....People.xml");
Then the XDocument's Load method loads the XmlReader object, building the LINQ to XML document structure:
XDocument xml = XDocument.Load(reader);
Similarly, the static Create method creates an XmlWriter object based on a StringWriter object:
StringWriter sw = new StringWriter(); XmlWriter w = XmlWriter.Create(sw);
This is necessary when you want to write the XML output to a string. The Save method provided by the XDocument class (the same as for the XElement class) accepts the XmlWriter object and stores the changes to the XML document in the StringWriter object.
Figure 3-10 shows that a new idperson element has been added to the source XML document.
Listing 3-20 previewed the code to modify an XML document. In the next section you'll see how to modify XML documents with XElement methods.
LINQ to XML provides all the methods needed to insert, modify, and delete elements in an existing XML document.
Inserting Elements in an XML Document
In the following code snippet (a duplicate of Listing 3-20) the code that adds a new element to the XML document appears in boldface:
XmlReader reader = XmlReader.Create(@"....People.xml"); XDocument xml = XDocument.Load(reader); Console.WriteLine(xml); XElement idperson = xml.Descendants("idperson").Last(); idperson.Add(new XElement("idperson", new XAttribute("id", 1), new XAttribute("year", 2006), new XAttribute("salaryyear", "160000,0000"))); StringWriter sw = new StringWriter(); XmlWriter w = XmlWriter.Create(sw); xml.Save(w); w.Close(); Console.WriteLine(sw.ToString());
To add an element to a specific position in an XML document, we have to navigate to the desired node using the Element or Descendants methods. Then, using the Add method of XElement, we can add the element in the right place.
NOTE
If you use the Add method without establishing a specific XML document position, the new element will be added at the end of the document.
We can use the AddFirst method to insert a new element at the beginning of an XML document, just after the root element, as in Listing 3-21.
XElement xml = XElement.Load(@"....People.xml"); xml.AddFirst(new XElement("person", new XElement("id",5), new XElement("firstname","Tom"), new XElement("lastname","Cruise"), new XElement("idrole",1))); Console.WriteLine(xml); |
Figure 3-11 shows the output for Listing 3-21.
Using the AddAfterSelf and AddBeforeSelf methods after positioning the cursor at the desired node, we can add a new element after or before the current node, respectively.
Updating Elements in an XML Document
Using the SetElementValue method of XElement, we can update an element with a new value. In Listing 3-22 we change the description of the first role contained in the People XML document.
XElement xml = XElement.Load(@"....People.xml"); XElement role = xml.Descendants("role").First(); Console.WriteLine("-=-=ORIGINAL-=-="); Console.WriteLine(role); role.SetElementValue("roledescription", "Actor"); Console.WriteLine(string.Empty); Console.WriteLine("-=-=UPDATED-=-="); Console.WriteLine(role); |
After having reached the first role node, we can use the SetElementValue method to change the role description to the Actor. (Figure 3-12 shows the output.)
Similarly, using the SetAttributeValue method of the XElement class we can change the value of the specified attribute. (See Listing 3-23.)
XElement xml = XElement.Load(@"....People.xml"); XElement role = xml.Descendants("idperson").First(); Console.WriteLine("-=-=ORIGINAL-=-="); Console.WriteLine(role); role.SetAttributeValue("year", "2006"); Console.WriteLine(string.Empty); Console.WriteLine("-=-=UPDATED-=-="); Console.WriteLine(role); |
As you can see from the output in Figure 3-13, the year of the first idperson element is changed from 2004 to 2006.
NOTE
Using the SetElementValue and SetAttributeValue methods with elements and attributes not already present in an XML document is equivalent to adding them to the current element. On the other hand, specifying a null value with an existing element removes that element from the XML document.
Finally, to replace an entire section of the XML document with new values, use the ReplaceNodes method of XElement, as in Listing 3-24.
XElement xml = XElement.Load(@"....People.xml"); xml.Element("person").ReplaceNodes(new XElement("id", 5), new XElement("firstname","Tom"), new XElement("lastname","Cruise"), new XElement("idrole",1)); Console.WriteLine(xml); |
The first person element is substituted with a new person, as shown in Figure 3-14.
Deleting Elements from an XML Document
The XElement class provides two methods to remove an element from an XML document: Remove and Remove Content.
The Remove method applied to the current node removes that element from the XML document. Using the Remove method with an IEnumerable<XElement> collection iterates through all the elements and removes them (see Listing 3-25).
XElement xml = XElement.Load(@"....People.xml"); xml.Descendants("idperson").First().Remove(); xml.Elements("role").Remove(); Console.WriteLine(xml); |
Figure 3-15 shows the output of this code snippet.
The second removal method that XElement provides is RemoveNodes, which lets you remove an entire section of an XML document. In Listing 3-26 all the content of the first role element is removed.
XElement xml = XElement.Load(@"....People.xml"); xml.Element("role").RemoveNodes(); Console.WriteLine(xml); |
As you can see from the output in Figure 3-16, the first role element loses its content and is replaced by an empty tag (<role />).
RemoveAttribute() called on the current element removes all its attributes. To remove just a single attribute, you have to set that attribute's value to null using the SetAttributeValue method, as mentioned in the note after Listing 3-23.
The final section of this chapter covers the integration aspects between two LINQ technologies: LINQ to XML and LINQ to SQL.
In Listing 3-27 we use a LINQ to SQL query to retrieve all the person rows from the database, which produces an XML file similar to the People.xml file.
PeopleDataContext people = new PeopleDataContext(); XElement xml = new XElement("people", from p in people.People select new XElement("person", new XElement("id", p.ID), new XElement("firstname", p.FirstName), new XElement("lastname", p.LastName), new XElement("idrole", p.IDRole))); Console.WriteLine(xml); |
Using a LINQ to SQL query as an argument to the XElement constructor, we can produce an XML document in which content comes directly from database data. Figure 3-17 shows the output of the code snippet in Listing 3-27.
Now consider taking an XML document as the data source and querying the database to search for new rows. If the XML document contains new rows not present in the database, the code in Listing 3-28 uses LINQ to SQL to add them to the database.
NOTE
To execute the code shown in Listing 3-28 you have to leave the code of Listing 3-27 uncommented. That's because the code in Listing 3-28 simply adds a new record into the xml object retrieved by the code in Listing 3-27.
xml.Add(new XElement("person", new XElement("id", 5), new XElement("firstname", "Tom"), new XElement("lastname", "Cruise"), new XElement("idrole", 1))); Console.WriteLine(xml); AddPerson(xml, people); |
Using the XML document created in Listing 3-27, the code adds a new person element and passes the final XElement object to the AddPerson method that checks the XML document for new records, eventually adding them to the database. Listing 3-29 shows the code of the AddPerson method.
private static void AddPerson(XElement xml, PeopleDataContext peopledb) { var people = xml.Descendants("person"); foreach(var person in people) { var query = from p in peopledb.People where p.ID == (int)person.Element("id") select p; if (query.ToList().Count == 0) { Person per = new Person(); per.FirstName = person.Element("firstname").Value; per.LastName = person.Element("lastname").Value; per.IDRole = (int)person.Element("idrole"); peopledb.People.Add(per); } } peopledb.SubmitChanges(); } |
The body of the AddPerson method retrieves the collection of person elements in the XML document:
var people = xml.Descendants("person");
Then it iterates through the collection and queries the People table by primary key for each person:
var query = from p in peopledb.People where p.ID == (int)person.Element("id") select p;
If the collection produced by the ToList method doesn't contain items, then the person is not in the table; in that case, a new Person object is created, filled with values read from the XML document, and added to People.
Once all the XML elements have been processed, the insertions are propagated to the database with SubmitChanges().
3.15.34.39