Chapter 11. LINQ Programming

Chapter 6, was a general introduction to Language Integrated Query (LINQ) and a review of LINQ to Objects. LINQ to Objects is the implicit LINQ provider, but other providers are available. Namely, LINQ to XML and LINQ to SQL are the more commonly used of these other providers. Because of the ubiquitous nature of Extensible Markup Language (XML) and SQL in .NET development, these two providers have particular importance. Both of them implement the IQueryable interface, which extends the IEnumerable<T> interface, to refine and implement the standard LINQ interface in the context of the provider. For example, LINQ to XML does more than query XML. You also can use LINQ to XML to browse an XML data store. LINQ to SQL also provides more than query functionality. You can perform SQL commands, such as insert, delete, and add operations.

This chapter demonstrates the extensible nature and strength of LINQ. In the future, the realm of LINQ will expand as additional providers are introduced. It will be the unifying model of data, in the most abstract of terms. LINQ probably will touch upon domains that have not even been envisioned in the hallways of Microsoft. There could be LINQ to Explorer, which could allow users to query files and directories with query expressions. LINQ to Internet could extend the concept of data mining to the Web. LINQ to Cloud could search for specific resources in your cloud. The possibilities are unlimited. Until then, we will focus on LINQ to XML and LINQ to SQL.

LINQ to XML

LINQ to XML manages XML data. XElement is the central component to LINQ to XML. XElement represents a collection of XML elements. You can load XML into an XElement component from memory using a string containing XML or another XElement object. XElement also can be loaded from a file using TextReader and XmlTextReader types. Conversely, you can persist XML to a string in memory, or you can persist XML to a file using the TextWriter or XmlTextWriter types.

As mentioned, LINQ to XML is about more than simply querying XML data. In addition to performing queries against XML stores, LINQ to XML presents a complete interface to validate, navigate, update, and otherwise manage XML data. Already, .NET supports competing application programming interfaces (APIs) for managing XML at various levels of sophistication and complexity: XmlTextReader and XmlTextWriter for reading and writing XML, XmlDocument for supporting the Document Object Model (DOM) for accessing XML, and finally XPath. Instead of supporting XML query capability only and deferring to another interface for other functionality, LINQ to XML provides a comprehensive interface to manage XML. This is consistent with the overall objective of LINQ to provide a unified syntax over various domains. Instead of forcing you to understand two models (LINQ to XML and something else), you can learn a single syntax and methodology for accessing XML.

XML Schemas

Validation using schemas is an essential ingredient of XML management. The mantra of garbage-in and garbage-out is well founded. The concept of well-formed XML is also important. Both validation and well-formed XML are verifiable in LINQ to XML.

Validation

You can validate XML against a schema with either XDocument.Validate or XElement.Validate. The schema is normally found in an .xsd file. Here is a simple schema that defines the book element, which requires an author attribute. The author attribute is of the string type:

<xsd:schema xmlns:xsd='http://www.w3.org/2001/XMLSchema'>
    <xsd:element name="book">
        <xsd:complexType>
            <xsd:attribute name="author"
                type="xsd:string" use="required"/>
        </xsd:complexType>
    </xsd:element>
</xsd:schema>

The following code is a short XML file that includes a book element. Notice that the author attribute has been omitted. Therefore, based on the schema, this is not a valid XML file:

<?xml version="1.0"?>
<book xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:noNamespaceSchemaLocation="file:///c:/code/validate.xsd">
</book>

The following program uses LINQ to XML to validate the XML file with the schema. XmlSchemaSet.Add reads the schema file. The XML file is read with the XDocument.Load method and then is validated with the XDocument.Validate method. The first parameter of the Validate method identifies the schema. The next parameter is the callback function that is called to handle schema errors. In this example, the callback is ReportSchemaError, which displays the error message to the Console window. The application has several LINQ-related namespaces. System.Linq is the core namespace for LINQ, while System.Xml.Linq is the core namespace for LINQ to XML. The System.Xml.Schema namespace contains the XmlSchemaSet type. Here is the code:

using System;
using System.Linq;
using System.Xml.Linq;
using System.Xml.Schema;
namespace Validate {
    class Program {
        static void Main(string[] args) {

            XmlSchemaSet schemas = new XmlSchemaSet();
            schemas.Add("", @"validate.xsd");
            XDocument xml = XDocument.Load(@"c:codevalidate.xml");

            xml.Validate(schemas, new ValidationEventHandler(ReportSchemaError));
        }

        private static void ReportSchemaError(object sender, ValidationEventArgs e) {
            Console.WriteLine(e.Message);
        }
    }
}

Here is the message that is displayed:

The required attribute 'author' is missing.

Here is a modified XML file. This file will pass the validation:

<?xml version="1.0"?>
<book xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:noNamespaceSchemaLocation="file:///c:/code/validate.xsd"
    author="Donis Marshall">
</book>

In XML, quotes are required around string properties. For that reason, the following attribute would not be well-formed XML; the quotes are missing:

author=Donis Marshall

The quality of the XML formation is checked by LINQ to XML when loading the document into memory, using, for example, the XDocument.Load function. If the document is not well formed, an XmlException is thrown at that time.

Navigation

Navigation XML is another capability of LINQ to XML. Navigation allows you to browse XML nodes. Data is only as useful as it is accessible. Classes derived from XNode form the set of object types that can be navigated or browsed. For LINQ to XML, nodes encompass every aspect of the XML. These types include:

  • Elements within a document (XElement)

  • Parent and child elements (XElement)

  • Element value (XText)

  • Comments (XComment)

  • Attributes of an element (XAttribute)

Table 11-1 lists the members of the XNode class related to navigation, which are common to the classes in the preceding list.

Table 11-1. XNode members pertaining to navigation

Member

Description

Ancestors

Returns the ancestors of the current element. The second overload restricts the result to elements of the specified name. Here are the signatures:

public IEnumerable<XElement> Ancestors()

public IEnumerable<XElement> Ancestors(XName name)

CreateNavigator

Creates a LINQ to XML cursor. (Cursors are discussed at the end of this section.) Here are the signatures:

public static XPathNavigator CreateNavigator(this XNode node)

public static XPathNavigator CreateNavigator(this XNode node,
    XmlNameTable nameTable)

ElementsAfterSelf

Returns siblings after the current element. The second overload restricts the results to elements of the specified name. Here are the signatures:

public IEnumerable<XElement> ElementsAfterSelf()

public IEnumerable<XElement> ElementsAfterSelf(XName name)

ElementsBeforeSelf

Returns siblings before the current element. The second overload restricts the results to elements of the specified name. Here are the signatures:

public IEnumerable<XElement> ElementsBeforeSelf()

public IEnumerable<XElement> ElementsBeforeSelf(XName name)

IsAfter

Indicates whether the current node is after the specified node. Here is the signature:

public bool IsAfter(XNode node)

IsBefore

Indicates whether the current node is before the specified node. Here is the signature:

public bool IsBefore(XNode node)

NodesAfterSelf

Returns nodes after the current node. Here is the signature:

public IEnumerable<XNode> NodesAfterSelf()

NodesBeforeSelf

Returns nodes before the current node. Here is the signature:

public IEnumerable<XNode> NodesBeforeSelf()

NextNode

Returns the next node. Here is the definition:

public XNode NextNode {get;}

Parent

Returns the parent of the current node. Here is the definition:

public XElement Parent {get;}

PreviousNode

Returns the previous node.

public XNode PreviousNode{get;}

In addition to being a valid XML node, XElement is an XML container class. Container classes inherit XContainer, which then inherits XNode. XDocument is another example of a container class and also inherits XContainer. Containers can manage nodes found in the container. Table 11-2 lists methods and properties of XContainer that help when navigating XML.

Table 11-2. XContainer members pertaining to navigation

Member

Description

DescendantNodes

Returns a collection of descendant nodes. The second overload restricts the results to elements of the specified name. Here are the signatures:

public IEnumerable<XElement> Descendants()

public IEnumerable<XElement> Descendants(XName name)

Descendants

Returns a collection of descendant elements. The second overload restricts the result to elements of the specified name. Here are the signatures:

public IEnumerable<XElement> Descendants()

public IEnumerable<XElement> Descendants(XName name)

FirstNode

Returns the first child node of the current element. Here is the definition:

public XNode FirstNode {get;}

LastNode

Returns the last child node of the current element. Here is the definition:

public XNode LastNode {get;}

XElement has additional members for navigation that are not inherited from XNode or XContainer. The list of these members is provided in Table 11-3.

Table 11-3. XElement navigation members

Member

Description

AncestorsAndSelf

Returns the current element and ancestors. The second overload restricts the result to elements of the specified name. Here are the signatures:

public IEnumerable<XElement> AncestorsAndSelf()

public IEnumerable<XElement> AncestorsAndSelf(XName name)

Attribute

Returns the specified attribute of the current element. The second overload restricts the results to nodes of the specified name. Here are the signatures:

public XAttribute Attribute(XName name)

public IEnumerable<XElement> AncestorsAndSelf(XName name)

Attributes

Returns the attributes of the current element. The second overload restricts the result to attributes of the specified name. Here are the signatures:

public IEnumerable<XAttribute> Attributes()

public IEnumerable< XAttribute > Attributes(XName name)

DescendantNodeAndSelf

Returns a collection of descendant nodes. Here is the signature:

public IEnumerable<XNode> DescendantsNodesAndSelf()

Element

Returns the specified child element. Here is the signature:

public XElement Element(XName name)

Elements

Returns the child elements of the current element. The second overload restricts the result to elements of the specified name. Here are the signatures:

public IEnumerable<XElement> Elements()

public IEnumerable<XElement> Elements(XName name)

FirstAttribute

Returns the first attribute of the current element. Here is the definition:

public XAttribute FirstAttribute {get;}

FirstNode

Returns the first child node of the current element. Here is the definition:

public XNode FirstNode {get;}

LastAttribute

Returns the last attribute of the current element. Here is the definition:

public XAttribute LastAttribute {get;}

LastNode

Returns the last child node of the current element. Here is the definition:

public XNode LastNode {get;}

XML data is hierarchical. XElement reflects that hierarchical nature of XML onto LINQ to XML. In most XML models, document is the key component. However, in LINQ to XML, the focus is XElement. You can enumerate all the elements of the document from an XElement object that refers to the root element. From there, you can continue to drill down through child elements, values, and attributes until the XML document has been explored fully.

The following code is a console application that enumerates elements of an XML file. The filename is provided as a command-line parameter. In Main, the XML file is loaded and the root element is displayed. The GetElements method is called next. In this method, the child elements are requested using the XElement.Descendants method. Then the elements are enumerated. GetElement is called recursively until all the elements have been rendered. The attributes, if any, of every element also are displayed:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
using System.Text;

namespace EnumerateXML
{
    class Program {
        static void Main(string[] args) {
            XElement xml = XElement.Load(args[0]);
            Console.WriteLine("Element: {0}", xml.Name);
            GetElements(xml);
        }

        static void GetElements(XElement xml) {
            foreach (XElement element in xml.Descendants()) {
                Console.WriteLine("Element: {0}", element.Name);
                foreach (XAttribute attribute in element.Attributes()) {
                    Console.WriteLine(" " + attribute.Name);
                }
                GetElements(element);
            }
        }
    }
}

The XPathNavigator is an alternate model for browsing using LINQ to XML. It uses a cursor to browse instead of requesting and then enumerating a collection of elements. The previous code example used the latter model. The cursor model is simpler and probably more descriptive. XNode.CreateNavigator returns an instance of an XPathNavigator. XPathNavigator has Move methods that move the cursor, such as MoveToAttribute, MoveToChild, and MoveToFollowing. The cursor can be moved forward and backward. MoveToParent and MoveToRoot are examples of methods that jump the cursor backward in the LINQ to XML. More important than navigation, the XPathNavigator type has methods that edit values and confirm relationships, such as between parents and children. The objective of the following example is similar to the previous example. This code browses an XML file using the XPathNavigator:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
using System.Xml.XPath;
using System.Text;

namespace PathNavigator {
    class Program {
        static void Main(string[] args) {
            XElement xml = XElement.Load(args[0]);
            XPathNavigator nav = ((XNode) xml).CreateNavigator();

            Console.WriteLine("Element: {0}", nav.Name);
            GetElements(nav);

        }

        static void GetElements(XPathNavigator nav) {
            while (nav.MoveToFollowing(XPathNodeType.Element)) {
                Console.WriteLine("Element: {0}", nav.Name);
                if (nav.HasAttributes) {
                    nav.MoveToFirstAttribute();
                    Console.WriteLine("{0}", nav.Name);
                    while (nav.MoveToNextAttribute())
                    Console.WriteLine("{0}", nav.Name);
                }
            }
        }
    }
}

Explicit Casting

XML elements can contain values. These values are outside the control of the Common Language Runtime (CLR) and therefore are not type-safe. Nonetheless, you can cast these values to .NET primitives. The conversion occurs at run time. An invalid cast will raise an exception at that time. Here is a simple XML file:

<data>
    <item>
        bob
    </item>
</data>

The following code reads the simple XML file and selects the child node, which is the item element. That element is then cast to a string in the Console.WriteLine method. The cast is successful because the value (bob) is convertible to a string. The next line is commented out, where the element is cast to an integer. That is invalid for this element and would raise a FormatException exception at run time. Of course, you would prefer to raise an exception at compile time:

XElement xml = XElement.Load(@"....simple.xml");
XElement child = (XElement)xml.FirstNode;
Console.WriteLine("Element: {0}", (string) child);
//Console.WriteLine("Element: {0}", (int) child);

Unlike XElement, the XPathNavigator type does not support explicit casting of element values. You have to use member properties to cast to the target type. The following simple XML file has been altered slightly. The item property now contains an integer value:

<data>
    <item>
        123
    </item>
</data>

The following code uses XPathNavigator to move to the child node and then to display the value. The ValueAsInt property is used to cast the value to an integer. Similar to explicit casting, this is not type-safe. Conversion problems occur at run time and cause a FormatException exception:

XElement xml = XElement.Load(@"....simple.xml");
XPathNavigator nav = ((XNode)xml).CreateNavigator();
nav.MoveToChild(XPathNodeType.Element);
Console.WriteLine("Element: {0}", nav.ValueAsInt);

XML Modification

You can change the content of XML data using LINQ to XML. In the case of an XML file, you can read the XML into memory, modify the data, and then save the changes back to a file. The XElement element has several methods that support modifying XML. This includes adding and changing nodes. Table 11-4 lists members of XElement that are useful in modifying an XML data file.

Table 11-4. XElement members pertaining to data modification

Member

Description

Add

Adds content to the current element, which could be a child element. Here are the signatures:

public void Add(object content)

public void Add(object[] content)

AddAfterSelf

Adds content after the current element. Here are the signatures:

public void AddAfterSelf(object content)

public void AddAfterSelf(object[] content)

AddAnnotation

Adds an annotation (a comment) to the current element. Here is the signature:

public void Annotation (object content)

AddBeforeSelf

Adds content before the current element. Here are the signatures:

public void AddBeforeSelf(object content)

public void AddBeforeSelf(object[] content)

AddFirst

Inserts content as the first child of the current element. Here are the signatures:

public void AddFirst(object content)

public void AddFirst(object[] content)

Remove

Removes the current element. Here is the signature:

public void Remove()

RemoveAll

Removes child nodes of the current element. Here is the signature:

public void RemoveAll()

RemoveAnnotations

Removes annotations of the type indicated from the current element. Here are the signatures:

public void RemoveAnnotations<T>() where T : class

public void RemoveAnnotations(Type type)

RemoveAttributes

Removes the attributes of the current element. Here is the signature:

public void RemoveAttributes()

RemoveNodes

Removes the nodes of the current element. Here is the signature:

public void RemoveNodes()

ReplaceAll

Replaces the children of the current element with the provided content. Here are the signatures:

public void ReplaceAll(object content)

public void ReplaceAll(object[] content)

ReplaceAttributes

Replaces the attributes of the current element with the provided content. Here are the signatures:

public void ReplaceAttributes(object content)

public void ReplaceAttributes(object[] content)

ReplaceNodes

Replaces the child nodes of the current element with the provided content. Here are the signatures:

public void ReplaceNodes(object content)

public void ReplaceNodes(object[] content)

Save

Saves XML data. SaveOptions enumeration has two values. SaveOptions.None indents the XML, while removing extraneous white space. SaveOptions.DisableFormatting persists the XML while preserving the formatting, including white space. Here are the signatures:

public void Save(string fileName)

public void Save(TextWriter textWriter)

public void Save(XmlWriter writer)

public void Save(string fileName, SaveOptions options )

public void Save(TextWriter textWriter, SaveOptions options)

SetAttributeValue

Adds, modifies, or deletes an attribute. If the attribute does not exist, it is added. Otherwise, the attribute is changed. If value is null, the attribute is deleted. Here is the signature:

public void SetAttributeValue(XName name, object value)

SetElementValue

Adds, modifies, or deletes a child element. If the element does not exist, it is added. If value is null, the element is deleted. Here is the signature:

public void SetElementValue(XName name, object value)

SetValue

Sets the value of the current element. Here is the signature:

public void SetValue(object value)

The following program demonstrates modifying XML data. It finds and replaces the value of an attribute or element. This is a console program where you specify the XML file, mode (attribute or element), find value, and replace value as command-line arguments—in that order. The mode is either attribute (or just a) or element (or just e). Results are saved back to the original file. In the application, the XML file is loaded with XElement.Load. In the element case, we enumerate the elements. Whenever a matching value is found, it is changed with the replace value. In the attribute case, the elements are enumerated. Within each element, the attributes are enumerated. If a matching value is found, it is changed with the replace value. After the switch statement, the XML file is updated using the XElement.Save method:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml.Linq;

namespace FindAndReplace {
    class Program {
        static void Main(string[] args) {
            XElement xml = XElement.Load(args[0]);
            char[] remove = { '	', '
', ' ' };
            switch (args[1].ToLower()) {
                case "element":
                case "e":
                    foreach (XElement element in xml.Elements()) {
                        if (args[2] == ((string)element).Trim(remove)) {
                            element.SetValue(args[3]);
                        }
                    }
                    break;
                case "attribute":
                case "a":
                    foreach (XElement element in xml.Elements()) {
                        foreach (XAttribute attribute in element.Attributes()) {
                            if (args[2] == ((string) (attribute)).Trim(remove)) {
                                attribute.SetValue(args[3]);
                            }
                        }
                    }
                    break;

            }
            xml.Save(args[0]);
        }
    }
}

XML Query Expressions

With LINQ to XML, you can apply LINQ query expressions to XML data. The query expression cannot be applied directly to an XML file. The file first must be read into memory as an XElement or an XDocument. Chapter 6 explained the syntax of query expressions. The XElement.Elements, XElement.Attributes, and other members of XDocument and XElement return enumerable collections, which can be sources of LINQ query expressions. Here is an example of a query expression using LINQ to XML:

var saleitems = from item in xml.Elements()
                where item.FirstAttribute.Value == "sale"
                orderby item.Element("discount").Value
                select item;
..................Content has been hidden....................

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