|
download source code
In the previous installment of this series on LINQ, you learned the basics of DLINQ
and saw how to use it to retrieve data from the database.
Specifically it focused on the retreival of relational data for display purposes. This installment will shift gears and
focus on the XML aspects of LINQ, known as XLINQ. To start with, you will learn the basics of XLINQ
(XML Query extensions of LINQ) and then we'll discuss the steps involved in using DLINQ in conjunction with XLINQ
to retrieve, transform, and display data. Along the way, you will also see the ways in which you can convert
relational data to hierachical data using XLINQ's functional construction capabilities.
Finally you will learn the steps involved in implementing complex data binding with
hierarchical data generated through XLINQ.
Introduction to XLINQ
XLINQ is a new in-memory XML programming API targeted at querying XML data using the standard query operators supplied with LINQ. XLINQ provides both DOM and XQuery/XPath like functionality in a consistent programming experience across the different LINQ-enabled data access technologies.
There are two important characteristics of XLINQ. First, as a member of the LINQ family of technologies, XLINQ provides a consistent query experience for querying XML data. Second, XLINQ exposes DOM functionalities in addition to supporting some of the XPath and XSLT features.
All of the XLINQ related functionalities are exposed through the classes present in the System.Xml.XLinq namespace. The below table provides an overview of the key classes contained in the System.Xml.XLinq namespace.
| Class |
Description |
| XAttribute |
Represents an XML attribute |
| XComment |
Represents an XML comment |
| XContainer |
Acts as the base class for the XDocument and XElement classes and provides the core methods for querying XML data |
| XDocument |
Represents an XML document, which acts as a logical container for the XML document |
| XElement |
Represents an XML element in an XML document |
| XNode |
Represents any item in the XML document that can be considered as a node in the XML document |
| XProcessingInstruction |
Represents an XML processing instruction |
| XText |
Represents a text node that is used for creating CDATA sections or working with mixed content |
Now that you have had an overview of the classes, the next section shows a simple example of using the XLINQ classes to create an XML document.
Simple XLINQ Example
In this section, you will discuss the creation of a simple XML document using the XML classes of the System.Xml.XLinq namespace. One of the new features of XML creation is the functional construction technique, which lets you create all or part of your XML tree in a single statement. By using functional construction with code indentation, you can visualize the structure of resultant XML, resulting in an increase in developers productivity. Let us look at an example that demonstrates the functional construction feature.
To start with , create a new Web site named AdvancedLINQ using the LINQ ASP.NET Web site template. Modify the newly created Web page code to look as follows:
<%@ Page Language="C#" %>
<%@ Import Namespace="System.Data.SqlClient" %>
<%@ Import Namespace="System.Xml.XLinq" %>
<%@ Import Namespace="System.Expressions" %>
<%@ Import Namespace="System.Query" %>
<script runat="server">
void btnCreateAndLoadXml_Click(object sender, EventArgs e)
{
XDocument bookStoreXml =
new XDocument(
new XDeclaration("1.0", "utf-8", "yes"),
new XComment("Categories XML Example"),
new XElement("Categories",
new XElement("Category",
new XAttribute("id", "1"),
new XAttribute("name", "Mountain Bikes"),
new XElement("Product",
new XAttribute("id", "2"),
new XAttribute("name", "Bearing Ball")
),
new XElement("Product",
new XAttribute("id", "3"),
new XAttribute("name", "Mountain-100 Silver, 38")
)
)
)
);
string xmlFileName = Server.MapPath("App_Data/Categories.xml");
bookStoreXml.Save(xmlFileName);
XElement element = XElement.Load(xmlFileName);
lblResults.Text = Server.HtmlEncode(element.Xml);
}
</script>
<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
<title>Creating a Simple XML Document using XLINQ</title>
</head>
<body>
<form id="form1" runat="server">
<div>
<asp:Button ID="btnCreateAndLoadXml" runat="server"
Text="Create & Load XML"
Width="127px" Height="41px"
OnClick="btnCreateAndLoadXml_Click" />
<br/>
<asp:Label runat="server" ID="lblResults" />
</div>
</form>
</body>
</html>
To start with, you import the required namespaces including the System.Xml.XLinq namespace that contains the core XLINQ classes.
In the Click event of the command button, you create an instance of the XDocument object and load it with data using the new functional construction feature of XLINQ. After creating the XML document, you save it to the local drive using the Save() method of the XDocument object passing in the XML file name as an argument. Once the file is saved to the local drive, it is also possible for you to load it back on to the memory. To accomplish this, you invoke the Load() method of the XElement.
Here is the XML produced by the ASP.NET page when you click on the button.
<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<!--Categories XML Example-->
<Categories>
<Category id="1" name="Mountain Bikes">
<Product id="2" name="Bearing Ball" />
<Product id="3" name="Mountain-100 Silver, 38" />
</Category>
</Categories>
Once you know how to work with the XLINQ classes, it then becomes very easy to use these classes in conjunction with DLINQ for transforming relational data structures into hierachical XML structures, which is the focus for the remainder of this article.
Using XLINQ with DLINQ
As shown in Part 2 of this article series, one of the first steps in using DLINQ is to generate the data access classes using the sqlmetal utility. If you install the May 2006 LINQ CTP edition, you can find the sqlmetal utility in the "< Drive_Name>\Program Files\LINQ Preview\Bin" folder. To generate the data access wrapper class for the objects contained in the AdventureWorks database, you use the below command in the command prompt.
sqlmetal /server:localhost /database:AdventureWorks
/user:username /password:password /code:AdventureWorks.cs
Once the AdventureWorks.cs file is created, place that inside the App_Code directory of the Web site.
Simple XML Data Binding
This section will demonstrate how to perform data binding with the XmlDataSource control using the XML data. This XML data is generated by transforming relational data from the AdventureWorks database to XML data.
To the ASP.NET Web site, add a new Web page and modify its code to look as follows.
<%@ Page Language="C#" %>
<%@ Import Namespace="System.Data.DLinq" %>
<%@ Import Namespace="System.Data.SqlClient" %>
<%@ Import Namespace="System.Xml.XLinq" %>
<%@ Import Namespace="System.Expressions" %>
<%@ Import Namespace="System.Query" %>
<%@ Import Namespace="System.Web.Configuration" %>
<script runat="server">
void Page_Load(object sender, EventArgs e)
{
if (!Page.IsPostBack)
{
string connectionString = WebConfigurationManager.ConnectionStrings
["AdventureWorks"].ConnectionString;
SqlConnection connection = new SqlConnection(connectionString);
AdventureWorks db = new AdventureWorks(connection);
XElement locations =
new XElement("Locations",
from location in db.Production.Location
select
new XElement("Location",
new XAttribute("ID", location.LocationID),
new XAttribute("Name", location.Name),
new XAttribute("CostRate", location.CostRate),
new XAttribute("Availability", location.Availability)
)
);
locationSource.Data = locations.Xml;
gridLocations.DataSource = locationSource;
gridLocations.DataBind();
}
}
</script>
<html xmlns="http://www.w3.org/1999/xhtml" >
<head id="Head1" runat="server">
<title>Simple Data retrieval using DLinq</title>
</head>
<body>
<form id="form1" runat="server">
<div>
<asp:XmlDataSource runat="Server" ID="locationSource"
XPath="Locations/Location"/>
<asp:GridView ID="gridLocations" runat="server"
AutoGenerateColumns="false"
HeaderStyle-BackColor="blue" HeaderStyle-ForeColor="White">
<Columns>
<asp:BoundField HeaderText="ID" DataField="ID"/>
<asp:BoundField HeaderText="Name" DataField="Name"/>
<asp:BoundField HeaderText="Cost Rate" DataField="CostRate"/>
<asp:BoundField HeaderText="Availability"
DataField="Availability"/>
</Columns>
</asp:GridView>
</div>
</form>
</body>
</html>
To start with, you retrieve the connection string from the Web.config file and use it to create an SqlConnection object. You then pass the SqlConnection object to the constructor of the AdventureWorks (data access class) class.
The most important part of the code is where you retrieve the data through the AdventureWorks class and convert that into XML format, all using one line of code.
XElement locations =
new XElement("Locations",
from location in db.Production.Location
select
new XElement("Location",
new XAttribute("ID", location.LocationID),
new XAttribute("Name", location.Name),
new XAttribute("CostRate", location.CostRate),
new XAttribute("Availability", location.Availability)
)
);
As you can see from the above, you can easily mix the data retreival DLINQ query with the functional construction feature of XLINQ.
You then get the XML representation of the data by invoking the Xml property of the XElement object and assign that as source to the XmlDataSource control.
locationSource.Data = locations.Xml;
Finally you set the DataSource of the GridView control to the XmlDataSource control and invoke its DataBind() method.
gridLocations.DataSource = locationSource;
gridLocations.DataBind();
Here is the output produced by the page when navigated through the browser.
Now that you have looked at a simple example of using DLINQ in conjunction with XLINQ, the next two examples build on this foundation and demonstrate advanced XLINQ queries for implementing data binding with a TreeView control.
Implementing Data Binding with Hierarchical Data
The last example discussed the data binding with a GridView control. It is also possible for you to bind the XML data to hierachical data bound controls such as a TreeView control. The below code snippet shows an example of how to accomplish this.
<%@ Page Language="C#" %>
<%@ Import Namespace="System.Data.DLinq" %>
<%@ Import Namespace="System.Data.SqlClient" %>
<%@ Import Namespace="System.Xml.XLinq" %>
<%@ Import Namespace="System.Expressions" %>
<%@ Import Namespace="System.Query" %>
<%@ Import Namespace="System.Web.Configuration" %>
<script runat="server">
void Page_Load(object sender, EventArgs e)
{
if (!Page.IsPostBack)
{
string connectionString = WebConfigurationManager.ConnectionStrings
["AdventureWorks"].ConnectionString;
SqlConnection connection = new SqlConnection(connectionString);
AdventureWorks db = new AdventureWorks(connection);
XElement categories =
new XElement("Categories",
from category in db.Production.ProductSubcategory
orderby category.ProductSubcategoryID
select
new XElement("Category",
new XAttribute("ID", category.ProductSubcategoryID),
new XElement("Details",
new XAttribute("Name", category.Name)
)
)
);
categorySource.Data = categories.Xml;
categoriesView.DataSource = categorySource;
categoriesView.DataBind();
}
}
</script>
<html xmlns="http://www.w3.org/1999/xhtml" >
<head id="Head1" runat="server">
<title>Simple Data Binding with TreeView</title>
</head>
<body>
<form id="form1" runat="server">
<div>
<asp:XmlDataSource runat="Server" ID="categorySource"
XPath="Categories/Category"/>
<asp:TreeView ExpandImageUrl="Images/closed.gif"
CollapseImageUrl="Images/open.gif"
ID="categoriesView" Runat="server">
<DataBindings>
<asp:TreeNodeBinding DataMember="Category" ValueField="ID"
TextField="ID"/>
<asp:TreeNodeBinding DataMember="Details" ValueField="Name"
TextField="Name"/>
</DataBindings>
</asp:TreeView>
</div>
</form>
</body>
</html>
In this example, you use a TreeView control to display the categories data retrieved from the ProductSubcategory table. For this reason, you create the XML document with the root element <Categories> and multiple child <Category> elements, with each <Category> element consisting of all the details of the category.
XElement categories =
new XElement("Categories",
from category in db.Production.ProductSubcategory
orderby category.ProductSubcategoryID
select
new XElement("Category",
new XAttribute("ID", category.ProductSubcategoryID),
new XElement("Details",
new XAttribute("Name", category.Name)
)
)
);
You bind the above XML output to the TreeView control, which results in the below output.
The above screenshot shows all the categories in the ProductSubcategory table from the AdventureWorks database.
Complex Data Binding using XLINQ
This section will build on the previous example and discuss how to create a complex XML hierarchy that includes all the categories and the corresponding products (that belong to each of the categories) and display them in a TreeView control. First step is to modify the code that dynamically constructs the XML to also include all the products.
<%@ Page Language="C#" %>
<%@ Import Namespace="System.Data.DLinq" %>
<%@ Import Namespace="System.Data.SqlClient" %>
<%@ Import Namespace="System.Xml.XLinq" %>
<%@ Import Namespace="System.Expressions" %>
<%@ Import Namespace="System.Query" %>
<%@ Import Namespace="System.Web.Configuration" %>
<script runat="server">
void Page_Load(object sender, EventArgs e)
{
if (!Page.IsPostBack)
{
string connectionString = WebConfigurationManager.ConnectionStrings
["AdventureWorks"].ConnectionString;
SqlConnection connection = new SqlConnection(connectionString);
AdventureWorks db = new AdventureWorks(connection);
XElement categories =
new XElement("Categories",
from category in db.Production.ProductSubcategory
orderby category.ProductSubcategoryID
select
new XElement("Category",
new XAttribute("ID", category.ProductSubcategoryID),
new XAttribute("Name", category.Name),
new XAttribute("ModifiedDate", category.ModifiedDate),
from product in category.Product
orderby product.Name
select
new XElement("Product",
new XAttribute("ID", product.ProductID),
new XAttribute("Name", product.Name)
)
)
);
categorySource.Data = categories.Xml;
categoriesView.DataSource = categorySource;
categoriesView.DataBind();
}
}
</script>
<html xmlns="http://www.w3.org/1999/xhtml" >
<head id="Head1" runat="server">
<title>Complex Hierarchical Binding with a TreeView control</title>
</head>
<body>
<form id="form1" runat="server">
<div>
<asp:XmlDataSource runat="Server" ID="categorySource"
XPath="Categories/Category"/>
<asp:TreeView ExpandImageUrl="Images/closed.gif"
CollapseImageUrl="Images/open.gif" ID="categoriesView"
Runat="server">
<DataBindings>
<asp:TreeNodeBinding DataMember="Category" ValueField="ID"
TextField="Name"/>
<asp:TreeNodeBinding DataMember="Product" ValueField="ID"
TextField="Name"/>
</DataBindings>
</asp:TreeView>
</div>
</form>
</body>
</html>
Let us discuss the important lines of code.
To be able to display the products under their corresponding categories, you need to ensure that
the resultant XML document reflects this meaning that each <Category> element should contain
all the products (that belong to that category) in the form of one or more <Product>
elements. You construct such a hierarchy by nesting the from..in..select statement for the products
inside the from..in..select statement for the categories.
XElement categories =
new XElement("Categories",
from category in db.Production.ProductSubcategory
orderby category.ProductSubcategoryID
select
new XElement("Category",
new XAttribute("ID", category.ProductSubcategoryID),
new XAttribute("Name", category.Name),
new XAttribute("ModifiedDate", category.ModifiedDate),
from product in category.Product
orderby product.Name
select
new XElement("Product",
new XAttribute("ID", product.ProductID),
new XAttribute("Name", product.Name)
)
)
);
Once you have the right XML structure, you can then easily bind that to the TreeView control using the same approach as discussed in the previous example. The resultant output is shown in the below screenshot.
Conclusion
In this part, we looked at the basics of XLINQ and talked about creating XML data on the fly. Then we looked at how to perform queries using DLINQ for data retreival and transform that output for XML data binding with an XmlDataSource control. Finally you also understood the steps involved in performing complex data binding with a TreeView control for displaying hierachical data.
|