3.3. Creating and Modifying XML Documents

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.

3.3.1. Creating an XML Document from Scratch

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 XElement object that will be added as child element

  • 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.

Example 3-16. Using the XDocument Class Constructor to Specify XML Declaration Attributes
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);

There is a bug in the XDeclaration constructor. Even if you set the encoding value to a value other than UTF-16, UTF-16 will be used.


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.

Figure 3-8. Functional construction allows us to easily create part of the People XML document.

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.

Example 3-17. Reproducing an XML Document Snippet That Has Microsoft Word Format
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.

Example 3-18. Transforming an XML Document into HTML Code
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).

Figure 3-9. The People XML document transformed into HTML

3.3.2. Loading and Saving XML

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

Example 3-19. Building an XML Document from a String
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().

Example 3-20. Reading an XML Document with XmlReader and Saving It with XmlWriter
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.

Figure 3-10. The salary element has a new idperson child

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.

3.3.3. Modifying XML

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.

Example 3-21. Using AddFirst to Insert a New Element at the Beginning of an XML Document
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.

Figure 3-11. Adding a new person element at the beginning of the XML document

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.

Example 3-22. Using the SetElementValue Method to Modify an Element's Value
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.)

Figure 3-12. The SetElementValue method changes the value of an XML element.

Similarly, using the SetAttributeValue method of the XElement class we can change the value of the specified attribute. (See Listing 3-23.)

Example 3-23. Changing the Value of an Attribute by Using the SetAttributeValue Method
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.

Figure 3-13. Just like the SetElementValue method changes an element's value by using the SetAttributeValue method, it can change an attribute's value

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.

Example 3-24. Replacing a Whole XML Document Section by Using the ReplaceNodes Method
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.

Figure 3-14. The output for Listing 3-24

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

Example 3-25. Removing an idperson Element and the role Section
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.

Figure 3-15. The output shows that the role section and an idperson element have been deleted.

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.

Example 3-26. Removing the Content of the First role Element
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 />).

Figure 3-16. The RemoveNodes method applied to the first role element

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.

3.3.4. LINQ to XML and LINQ to SQL

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.

Example 3-27. Querying the SQL Server People Database to Produce an XML Document from a LINQ to SQL Query
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.

Figure 3-17. The XML document produced by querying the People database

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.

Example 3-28. Searching for New Records in an XML Document and Eventually Adding Them to the Database
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.

Example 3-29. Adding a New Person Record, Read from the XML Document, That Is Not Already Present in the Database
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().

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.15.34.39