In this section, you’ll learn how to create and read an XML file using LINQ to XML. We decided not to use the System.XmlAPIs because they’re obsolete and are maintained in .NET Framework 4.0 for compatibility reasons only.
Writing XML
When LINQ to XML was designed, one of the goals was to make it simpler by using fewer classes and less code to create and read a file. The result was the set of classes shown in figure B.3.
TECHNIQUE 105
XCData Class XText
XObject Abstract Class
XAttribute Abstract Class XObject
XComment Class XNode
XDocument Class XContainer
XElement Class XContainer XText
Class XNode
XContainer Abstract Class XNode
XProcessingInst...
Class XNode
XDocumentType Class
XNode XNode
Abstract Class XObject IXmlLineInfo
IXmlSerializable
Figure B.3 The classes in LINQ to XML
457 TECHNIQUE 105 Writing XML
Without going into each class in detail, you need to know that each of them is included in the System.Xml.Linq namespace and that the following ones are the most important:
■ XDocument—Represents the XML
■ XElement—Represents a node in the XML
■ XComment—Represents a comment
■ XAttribute—Represents an attribute
■ XDeclaration—Represents the XML declaration
You will surely use the other classes, but in our experience, 90% of the time these are the ones you’ll use the most.
PROBLEM
You need to create an XML file that contains the ID and the name of a given customer.
This file must be saved on the server machine for further processing.
SOLUTION
Creating an XML file using LINQ to XML is pretty easy. You have to create an XDocu- ment instance and, in its constructor, pass the XElement instances that create the root node. In turn, the XElement constructor takes a list of XElement instances that repre- sent the children. Thanks to this design, you can create the whole XML in a single statement, nesting XElement instances. If it’s well indented, such code is extremely easy to understand, as you can see in the next listing.
C#:
XDocument doc = new XDocument(
new XDeclaration("1.0", "utf-8", "true"), new XComment("comment"),
new XElement("Customers", new XElement("Customer",
new XAttribute("Id", "ALFKI"), new XElement("Name", "NAME") )
) );
VB:
Dim doc As New XDocument(
New XDeclaration("1.0", "utf-8", "true"), New XComment("comment"),
New XElement("Customers", New XElement("Customer",
New XAttribute("Id", "ALFKI"), New XElement("Name", "NAME") )
) )
First, you create an XDocument instance and then nest an XDeclaration element and an XComment element. Next, you nest an XElement (Customers) inside XDocument. Listing B.6 Creating an XML structure
458 APPENDIX B Data access fundamentals
Inside the XElement, you put another XElement (Customer) to which you add an Attri- bute (Id) and another XElement (Name). The result is the following:
<?xml version="1.0" encoding="utf-8"?>
<!—comment-->
<Customers>
<Customer Id="ALFKI">
<Name>Name</Name>
</Customer>
</Customers>
After you have the XDocument instance with the XML shown in the previous snippet, you can use the Save method to save the XML string in a file placed wherever you want:
C#:
doc.Save(path);
VB:
doc.Save(path)
If you compare the code needed to write an XML file using LINQ to XML with the code using System.Xml classes, you’ll understand that there’s no contest: LINQ to XML is better.
DISCUSSION
This sample has clearly demonstrated that LINQ to XML is well designed and easy to use. However, the benefits of such technology don’t end here—there’s more to learn.
Creating a simple XML structure on the fly with LINQ to XML is easy, but the real power comes when you have to create a structure starting with a set of data.
Generating XML from a data source
Have you ever happened to have a list of classes coming from a query or any other source, and you need to transform its data into XML? Well, we have. Once again, Sys- tem.Xml classes make this task a real nightmare because lots of code is needed, but LINQ to XML keeps things small and easy.
PROBLEM
You have a page where the user submits a file containing the ID of all the customers they need information about. You have to retrieve these customers and create an XML file with the name and the full address for each of them.
SOLUTION
The solution here is split in two phases: the first one retrieves the customers from the database, and the second one creates the XML. We’ll skip the first one and concen- trate on the second.
In the previous section, you learned that you can pass a list of elements to the XElement constructor. By exploiting this feature, you can use a LINQ query to pass child elements and nest data. The following listing shows this technique.
TECHNIQUE 106
459 TECHNIQUE 107 Reading XML
C#:
var customers = CustomersFromAQuery();
var doc = new XDocument(
new XElement("Customers", from c in customers
select new XElement("Customer",
new XElement("Name", c.CompanyName), new XElement("Address", c.Address), new XElement("PostalCode", c.PostalCode), new XElement("City", c.City),
new XElement("Country", c.Country) )
) );
VB:
Dim customers = CustomersFromAQuery() Dim doc =
New XDocument(
New XElement("Customers", From c In customers
Select New XElement("Customer",
New XElement("Name", c.CompanyName), New XElement("Address", c.Address), New XElement("PostalCode", c.PostalCode), New XElement("City", c.City),
New XElement("Country", c.Country) )
) )
The nested LINQ query contains the structure of each Customer node. We don’t know what you think, but the first time we wrote code like this, we almost cried thinking about the power and simplicity of it.
DISCUSSION
The capability and extreme simplicity of creating simple structures (and complex ones) and the readability of the code make LINQ to XML one of the wonderful gems of the .NET Framework. Now that you know how to create an XML structure, let’s turn to how to read it.
Reading XML
As the name says, LINQ to XML enables you to execute LINQ queries over the XML structure. In this case, you don’t have a set of classes, so you don’t have type safety.
This issue isn’t a significant problem because you can still perform queries in an untyped way.
Listing B.7 Creating an XML structure from a list of objects
TECHNIQUE 107
460 APPENDIX B Data access fundamentals
PROBLEM
Suppose that you have to read the XML file you already generated. You need to extract all customers in France for special processing, and after that you have to process all customers in alphabetic order.
SOLUTION
To retrieve the customers in France, you have to follow these steps:
1 Create the XDocument using the XML.
2 Navigate the XElement elements to reach the Customer ones.
3 Take the XElement instances that have a child XElement whose name is Country and whose value is France.
The next listing translates these actions into code.
C#:
var doc = XDocument.Load(path);
var france = doc.Root.Elements("Customer")
.Where(c => c.Element("Country").Value == "France") .Select(c =>
new {
Name = c.Element("Name").Value, Country = c.Element("Country").Value }
);
VB:
Dim doc = XDocument.Load(path)
Dim france = doc.Root.Elements("Customer").
Where(Function(c) c.Element("Country").Value = "France").
Select(Function(c) _ New With { _
.Name = c.Element("Name").Value, _ .Country = c.Element("Country").Value _ }
)
The Root property of the XDocument object represents the root node, and the Ele- ments property gives access to the children of the root (Customer). You then filter them using a lambda expression B. Unfortunately, the lambda expression code isn’t strongly typed because you’re working with XML, which is a loose-typed data format.
Naturally, we can use any LINQ method in LINQ to XML syntax. For this reason, we can use the OrderBy method to sort the customers by their name, as required in the Problem section. The following listing shows the required code.
C#:
var sorted = doc.Root.Elements("Customer") .OrderBy(c => c.Element("Name").Value) Listing B.8 Reading customers located in France
Listing B.9 Sorting customers by name
Perform filter
B
Perform filter
B
461 Summary
.Select(c =>
new {
Name = c.Element("Name").Value, Country = c.Element("Country").Value }
);
VB:
Dim sorted = doc.Root.Elements("Customer").
OrderBy(Function(c) c.Element("Name").Value).
Select(Function(c) New With { _ .Name = c.Element("Name").Value, _ .Country = c.Element("Country").Value _ })
Using the same mechanism, you can write any query you like. You’re not obliged to pour data into objects; you can do whatever you want—even leave original queried elements as XElement objects.
DISCUSSION
Reading an XML file using LINQ isn’t as easy as reading data from a list of objects, but it’s far better than manually iterating over elements or using an XPath expression (which is supported by LINQ to XML). If you’ve already worked with System.Xml classes, you’ll surely agree.
You’ve learned that LINQ to XML is a great technology for both reading and writ- ing data. One day you’re going to find yourself struggling against XML; we hope that you’ll remember that LINQ to XML is the best tool in your toolbox.