380 Chapter 8 • Using XML in the .NET Framework
the collection by using the GetElementsByTagName(“ProductName”) method of the XmlDataDocument object. Finally, it is just a matter of iterating through the productNames collection and loading each of its members in the list box.
At this stage, you will probably ask why we are not finding the unit price of the selected product. Actually, therein lies the beauty of the XmlDataDocument. Because it has extended the XmlDocument class, all of the members of the XmlDocument class are also available to us.Thus, we could use the same technique as shown in our previous example to find the price. Nevertheless, the reason for not showing the searching technique here is that we will cover it later when we discuss the XPathIterator object.
Figure 8.25 XmlDataDocument1.aspx
<!—\Chapter8\xmlDataDocument1.aspx —>
<%@ Page Language = "VB" Debug ="True" %> <%@ Import Namespace="System.Xml" %> <html><head></head><body><form runat="server"> Select a Product: <br/>
<asp:ListBox id="lstProducts" runat="server" rows = "2" /><br/><br/> </body></form><html>
<Script Language="vb" runat="server">
Sub Page_Load(s As Object, e As EventArgs)
If Not Page.IsPostBack Then
Dim myDataDoc As New XmlDataDocument() myDataDoc.Load(Server.MapPath("Catalog2.xml"))
Dim productNames As XmlNodeList
productNames= myDataDoc.GetElementsByTagName("ProductName") Dim x As XmlNode
For Each x In productNames lstProducts.Items.Add (x.FirstChild().Value) Next
End If End Sub </Script>
Using XML in the .NET Framework • Chapter 8 |
381 |
Using the Relational View of
an XmlDataDocument Object
In this example, we will process and display the Catalog3.xml document’s data as a relational table in a DataGrid. The Catalog3.xml is exactly the same as Catalog2.xml except that it has more data.The Catalog3.xml file is available in the accompanying CD.The output of this example is shown in Figure 8.26.
Figure 8.26 Output of XmlDataDocument DataSet View Example
If we want to process the XML data as relational data, we need to load the schema of the XML document first.We have generated the following schema for the Catalog3.xml using VS.NET. The schema specification is shown in Figure 8.27 (also available in the accompanying CD).
Figure 8.27 Catalog3.xsd
<xsd:schema id="Catalog" targetNamespace="http://tempuri.org /Catalog3.xsd" xmlns="http://tempuri.org/Catalog3.xsd" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata ="urn:schemas-microsoft-com:xml-msdata" attributeFormDefault ="qualified" elementFormDefault="qualified">
<xsd:element name="Catalog" msdata:IsDataSet="true" msdata:EnforceConstraints="False">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded"> <xsd:element name="Product">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="ProductID" type="xsd:string" minOccurs="0"
Continued
382 Chapter 8 • Using XML in the .NET Framework
Figure 8.27 Continued
msdata:Ordinal="0" />
<xsd:element name="ProductName" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="ListPrice" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="Type" form="unqualified" type="xsd:string"/> <xsd:attribute name="SupplierId" form="unqualified"
type="xsd:string" /> </xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
NOTE
When we create a schema from a sample XML document, VS.NET automatically inserts an xmlns attribute to the root element. The value of this attribute specifies the name of the schema. Thus when we created the schema for Catalog3.xml, the schema was named Catalog3.xsd and VS.NET inserted the following attributes in the root element of Catalog3.xml:
<Catalog xmlns="http://tempuri.org/Catalog3.xsd">
In our .aspx code, we loaded the schema using the ReadXmlSchema method of our XmlDataDocument object as:
myDataDoc.DataSet.ReadXmlSchema(Server.MapPath("Catalog3.xsd")).
Next, we have loaded the XmlDataDocument as:
myDataDoc.Load(Server.MapPath("Catalog3.xml")).
Using XML in the .NET Framework • Chapter 8 |
383 |
Since the DataDocument provides two views, we have exploited its DataSet
.Table(0) property to load the DataGrid and display our XML file’s information in the grid.The complete listing of the code is shown in Figure 8.28.The code is also available in the XmlDataDocDataSet1.aspx file in the accompanying CD.
Figure 8.28 Complete Listing XmlDataDocDataSet1.aspx
<!— Chapter8\XmlDataDocDataSet1.aspx —> <%@ Page Language = "VB" Debug ="True" %> <%@ Import Namespace="System.Xml" %>
<%@ Import Namespace="System.Data" %> <html><head></head><body><form runat="server"> Select a Product: <br/>
<asp:DataGrid id="myGrid" runat="server"/> </body></form></html>
<Script Language="vb" runat="server">
Sub Page_Load(s As Object, e As EventArgs)
If Not Page.IsPostBack Then
Dim myDataDoc As New XmlDataDocument()
' load the schema
myDataDoc.DataSet.ReadXmlSchema(Server.MapPath("Catalog3.xsd"))
' load the xml data
myDataDoc.Load(Server.MapPath("Catalog3.xml"))
myGrid.DataSource = myDataDoc.DataSet.Tables(0)
myGrid.DataBind()
End If End Sub </Script>
Viewing Multiple Tables of
a XmlDataDocument Object
In many instances, an XML document may contain nested elements. Suppose that a bank has many customers, and a customer has many accounts.We have modeled this simple scenario in an XML document with nested elements.This
384 Chapter 8 • Using XML in the .NET Framework
document, named Bank1.xml, is shown in Figure 8.29. It is also available in the accompanying CD.
Figure 8.29 Bank1.xml
<?xml version="1.0" encoding="utf-8" ?> <Bank xmlns="http://tempuri.org/Bank1.xsd">
<Customer>
<CustomerID>C100</CustomerID> <CustomerName>Alfred Smith</CustomerName> <City>Toledo</City>
<Account>
<Type>Savings</Type>
<Balance>1500.00</Balance>
</Account>
<Account>
<Type>Checking</Type>
<Balance>111.11</Balance>
</Account>
<Account>
<Type>Home Equity</Type> <Balance>50000</Balance>
</Account>
</Customer>
<Customer> —- —- —- —- —- —-
</Customer>
</Bank>
If we load the above XML document and its schema in an XmlDataDocument object, it will provide two relational tables’ views: one for the customer’s information, and the other for the account’s information. Our objective is to display the data of these relational tables in two DataGrids as shown in Figure 8.30.
To develop this application, first we had to generate the schema for our Bank1.xml file.We used the VS.NET XML designer to accomplish this task. It is
Using XML in the .NET Framework • Chapter 8 |
385 |
interesting to observe that while creating the schema,VS.NET automatically generates the 1:Many relationship between the Customer and Accounts elements.To establish the relationship, it also creates an auto-numbered primary key column (Customer_Id) in the Customer DataTable. Simultaneously, it inserts the appropriate values of the foreign keys in the Account DataTable.The DataSet view of the generated schema is shown in Figure 8.31.
Figure 8.30 Displaying Customer and Accounts Data in Two Data Grids
Figure 8.31 XmlDataDocument DataSet Representation in Visual Studio .NET
In order to provide the relational view of our XML document (Bank1.xml), VS.NET included the Customer_Id attributes in both Customer and Account elements in its generated schema. It also generated the necessary schema entries to
386 Chapter 8 • Using XML in the .NET Framework
describe the implied relationship among the Customer and Account elements. Figure 8.32 shows an excerpt of the generated schema for our XML file.The complete schema is available in a file named Bank1.xsd in the accompanying CD.
Figure 8.32 Primary Key and Foreign Key Specifications in the Bank1.xsd
<xsd:unique name="Constraint1" msdata:PrimaryKey="true"> <xsd:selector xpath=".//Customer" /> <xsd:field xpath="@Customer_Id" /></xsd:unique>
<xsd:keyref name="Customer_Account" refer="Constraint1"msdata:IsNested="true">
<xsd:selector xpath=".//Account" /> <xsd:field xpath="@Customer_Id" />
</xsd:keyref>
In the above fragment of the generated schema, the xsd:unique element specifies the Customer_Id attribute as the primary key of the Customer element. Subsequently, the xsd:keyref element specifies the Customer_Id attribute as the foreign key of the Account element. XPath expressions have been used to achieve the afore-mentioned objectives.
The complete listing of the application is shown in Figure 8.33. It is also available in the xmlDataDocDataSet2.aspx file in the accompanying CD.The code is pretty straightforward.We have loaded two data grids from two DataTables of the DataSet, associated with the XmlDataDocument object.
Figure 8.33 Complete Code of XmlDataDocDataSet2.aspx
<!— Chapter8\XmlDataDocDataSet2.aspx —> <%@ Page Language = "VB" Debug ="True" %> <%@ Import Namespace="System.Xml" %>
<%@ Import Namespace="System.Data" %> <html><head></head><body><form runat="server"> Customers : <br/>
<asp:DataGrid id="myCustGrid" runat="server"/><br/> Accounts : <br/>
<asp:DataGrid id="myAcctGrid" runat="server"/><br/> </body></form></html>
<Script Language="vb" runat="server">
Continued
Using XML in the .NET Framework • Chapter 8 |
387 |
Figure 8.33 Continued
Sub Page_Load(s As Object, e As EventArgs)
If Not Page.IsPostBack Then
Dim myDataDoc As New XmlDataDocument()
'load the schema myDataDoc.DataSet.ReadXmlSchema(Server.MapPath("Bank1.xsd"))
'load the xmldata myDataDoc.Load(Server.MapPath("Bank1.xml")) myCustGrid.DataSource = myDataDoc.DataSet.Tables("Customer") myCustGrid.DataBind()
'load the Account grid
myAcctGrid.DataSource = myDataDoc.DataSet.Tables("Account") myAcctGrid.DataBind()
End If End Sub </Script>
NOTE
In a Windows Form, the DataGrid control by default provides automatic drill-down facilities for two related DataTables. Unfortunately, it does not work in this fashion in a Web form. Additional programming is needed to simulate the drill-down functionality.
In this example, we have illustrated how an XmlDataDocument object maps nested XML elements into multiple DataTables.Typically, an element is mapped to a table if it contains other elements. Otherwise, it is mapped to a column.
Attributes are mapped to columns. For nested elements, the system creates the relationship automatically.
388 Chapter 8 • Using XML in the .NET Framework
Querying XML Data Using
XPathDocument and XPathNavigator
The XmlDocument and the XmlDataDocument have certain limitations. First of all, the entire document needs to be loaded in the cache. Often, the navigation process via the DOM tree itself gets to be clumsy.The navigation via the relational views of the data tables may not be very convenient either.To alleviate these problems, the XML.NET has provided the XPathDocument and XPathNavigator classes.These classes have been implemented using the W3C XPath 1.0 Recommendation (www.w3.org/TR/xpath).
The XPathDocument class enables you to process the XML data without loading the entire DOM tree. An XPathNavigator object can be used to operate on the data of an XPathDocument. It can also be used to operate on XmlDocument and XmlDataDocument. It supports navigation techniques for selecting nodes, iterating over the selected nodes, and working with these nodes in diverse ways for copying, moving, and removal purposes. It uses XPath expressions to accomplish these tasks.
The W3C XPath 1.0 specification outlines the query syntax for retrieving data from an XML document.The motivation of the framework is similar to SQL; however, the syntax is significantly different. At first sight, the XPath query syntax may appear very complex. But with a certain amount of practice, you may find it very concise and effective in extracting XML data.The details of the XPath specification are beyond the scope of this chapter. However, we will illustrate several frequently used XPath query expressions. In our exercises, we will illustrate two alternative ways to construct the expressions.The first alternative follows the recent XPath 1.0 syntax.The second alternative follows XSL Patterns, which is a precursor to XPath 1.0. Let us consider the following XML document named Bank2.xml.The Bank2.xml document is shown in Figure 8.34, and it is also available in the accompanying CD. It contains data about various accounts. We will use this XML document to illustrate our XPath queries.
Figure 8.34 Bank 2.xml
<!-- Chapter8\Bank2.xml -->
<Bank>
<Account>
<AccountNo>A1112</AccountNo>
<Name>Pepsi Beagle</Name>
Continued
Using XML in the .NET Framework • Chapter 8 |
389 |
Figure 8.34 Continued
<Balance>1200.89</Balance>
<State>OH</State>
</Account>
------ ---
------ ---
<Account>
<AccountNo>A7833</AccountNo> <Name>Frank Horton</Name> <Balance>8964.55</Balance> <State>MI</State>
</Account>
</Bank>
Sample Query Expression 1: Suppose that we want the names of all account holders.The following alternative XPath expressions will accomplish the job equally well:
■Alternative 1: descendant::Name
■Alternative 2: Bank/Account/Name
The first expression can be read as “Give me the descendents of all Name nodes.”The second expression can be read as “Give me the Name nodes of the Account nodes of the Bank node.” Both of these expressions will return the same node set.
Sample Query Expression 2: We want the records for all customers from Ohio.We may specify any one of the following expressions:
■Alternative 1: descendant::Account[child::State=’OH’]
■Alternative 2: Bank/Account[child::State=’OH’]
Sample Query Expression 3: Any one of the following alternative expressions will return the Account node-sets for all accounts with a balance more than 5000.00:
■Alternative 1: descendant::Account[child::Balance > 5000]
■Alternative 2: Bank/Account[child::Balance > 5000.00]