Evjen c10.tex V2 - 01/28/2008 2:13pm Page 547 Chapter 10: Working with XML and LINQ to XML xw.WriteEndElement(); } } Response.ContentType = "text/xml"; x.Save(Response.Output); This code creates an XmlDocument called Customers . Then it executes the SQL command and retrieves the XML data into an XmlReader .An XPathNavigator is created from the XmlDocument , and a child node is prepended to the document. A single call to the WriteNode method of the XmlWriter retrieved from the XPathDocument moves the entire XML fragment into the well-formed XmlDocument .Because the SQL statement contained from Customers as Customer as a table alias, each XML element is named < Customer >. Then, for this example, the resulting XML document is output directly to the Response object. You see the resulting XML in the browser shown in Figure 10-10. Figure 10-10 Of course, it’s nice to see the resulting XML, but it’s far more useful to style that information with XSLT. The XML Web Server control mentioned earlier is perfect for this task. However, in Listing 10-22, rather than setting both the TransformSource and DocumentSource properties as in Listing 10-25, you set only the TransformSource property at design time, and the XmlDocument is the one created in the code-behind of Listing 10-21. 547 Evjen c10.tex V2 - 01/28/2008 2:13pm Page 548 Chapter 10: Working with XML and LINQ to XML Listing 10-22: The ASPX Page and XSLT to style the XML from SQL Server ASPX <%@ Page Language="C#" CodeFile="Default.aspx.cs" Inherits="Default_aspx" %> <asp:xml id="Xml1" runat="server" transformsource="~/customersToHtml.xslt"/> XSLT <?xml version="1.0" encoding="utf-8" ?> <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0"> <xsl:output method="html"/> <xsl:template match="/"> <h3>List of Customers</h3> <table border="1"> <tr> <th>Company Name</th><th>Contact Name</th><th>Contact Title</th> </tr> <xsl:apply-templates select="//Customer"/> </table> </xsl:template> <xsl:template match="Customer"> <tr> <td><xsl:value-of select="CompanyName"/></td> <td><xsl:value-of select="ContactName"/></td> <td><xsl:value-of select="ContactTitle"/></td> </tr> </xsl:template> </xsl:stylesheet> VB ’Response.ContentType = "text/xml" ’x.Save(Response.Output) Xml1.XPathNavigator = xpathnav C# //Response.ContentType = "text/xml"; //x.Save(Response.Output); Xml1.XPathNavigator = xpathnav; In the code-behind file, the lines that set ContentType and write the XML to the Response object are commented out, and instead the XPathNavigator from the XmlDocument that is manipulated in Listing 10-21 is set as a property of the XML Web Server control. The control then performs the XSLT Stylesheet transformation, and the results are output to the browser, as shown in Figure 10-11. You have an infinite amount of flexibility within the System.Xml , System.Xml.Linq ,and System.Data namespaces. Microsoft has put together a fantastic series of APIs that interoperate beautifully. When you’re creating your own APIs that expose or consume XML, compare them to the APIs that Microsoft has provided — if you expose your data over an XmlReader or IXPathNavigable interface, you are sure to make your users much happier. Passing XML around with these more flexible APIs (rather than as simple and opaque strings) provides a much more comfortable and intuitive expression of the XML information set. Remember that the XmlReader that is returned from SqlCommand .ExecuteXmlReader() is holding its SQL connection open, so you must call Close() 548 Evjen c10.tex V2 - 01/28/2008 2:13pm Page 549 Chapter 10: Working with XML and LINQ to XML when you’re done using the XmlReader . The easiest way to ensure that this is done is the using statement. An XmlReader implements IDisposable and calls Close() for you as the variable leaves the scope of the using statement. Figure 10-11 SQL Server 2005 and the XML Data Type You’ve seen that retrieving data from SQL Server 2000 is straightforward, if a little limited. SQL Server 2005, originally codenamed Yukon, includes a number of very powerful XML-based features. Dare Obasanjo, a former XML Program Manager at Microsoft has said, ‘‘The rise of the ROX [Relational-Object-XML] database has begun.’’ SQL Server 2005 is definitely leading the way. One of the things that is particularly tricky about mapping XML and the XML information set to the relational structure that SQL Server shares with most databases is that most XML data has a hierarchical structure. Relational databases structure hierarchical data with foreign key relationships. Relational data often has no order, but the order of the elements within XmlDocument is very important. SQL Server 2005 introduces a new data type called, appropriately, XML . Previously, data was stored in an nvarchar or other string-based data type. SQL Server 2005 can now have a table with a column of type XML ,andeach XML data type can have associated XML Schema. 549 Evjen c10.tex V2 - 01/28/2008 2:13pm Page 550 Chapter 10: Working with XML and LINQ to XML The FOR XML syntax is improved to include the TYPE directive, so a query that includes FOR XML TYPE returns the results as a single XML-typed value. This XML data is returned with a new class called System.Data.SqlXml . It exposes its data as an XmlReader retrieved by calling SqlXml.CreateReader ,so you’ll find it to be very easy to use because it works like all the other examples you’ve seen in this chapter. In a DataSet returned from SQL Server 2005, XML data defaults to being a string unless DataAdapter.ReturnProviderSpecificTypes = true is set or a schema is loaded ahead of time to specify the column type. The XML data type stores data as a new internal binary format that is more efficient to query. The pro- grammer doesn’t have to worry about the details of how the XML is stored if it continues to be available on the XQuery or in XmlReader . You can mix column types in a way that was not possible in SQL Server 2000. You’re used to returning data as either a DataSet or an XmlReader . With SQL Server 2005, you can return a DataSet where some columns contain XML and some contain traditional SQL Server data types. Generating Custom XML from SQL 2005 You’ve seen a number of ways to programmatically generate custom XML from a database. Before read- ing this book, you’ve probably used FOR XML AUTO to generate fairly basic XML and then modified the XML in post processing to meet your needs. This was formerly a very common pattern. FOR XML AUTO was fantastically easy; and FOR XML EXPLICIT , a more explicit way to generate XML, was very nearly impossible to use. SQL Server 2005 adds the new PATH method to FOR XML , which makes arbitrary XML creation available to mere mortals. SQL 2005’s XML support features very intuitive syntax and very clean namespace handling. Here is an example of a query that returns custom XML. The WITH XMLNAMESPACES commands at the start of the query set the stage by defining a default namespace and using column-style name aliasing to asso- ciate namespaces with namespace prefixes. In this example, addr :istheprefixfor urn:hanselman.com/ northwind/address . use Northwind; WITH XMLNAMESPACES ( DEFAULT ’urn:hanselman.com/northwind’ , ’urn:hanselman.com/northwind/address’ as "addr" ) SELECT CustomerID as "@ID", CompanyName, Address as "addr:Address/addr:Street", City as "addr:Address/addr:City", Region as "addr:Address/addr:Region", PostalCode as "addr:Address/addr:Zip", Country as "addr:Address/addr:Country", ContactName as "Contact/Name", ContactTitle as "Contact/Title", Phone as "Contact/Phone", Fax as "Contact/Fax" FROM Customers FOR XML PATH(’Customer’), ROOT(’Customers’), ELEMENTS XSINIL 550 Evjen c10.tex V2 - 01/28/2008 2:13pm Page 551 Chapter 10: Working with XML and LINQ to XML The aliases using the AS keyword declaratively describe the elements and their nesting relationships, whereas the PATH keyword defines an element for the Customers table. The ROOT keyword defines the root node of the document. The ELEMENTS keyword, along with XSINIL , describes how you handle null. Without these keywords, no XML element is created for a row’s column that contains null; this absence of data in the database causes the omission of data in the resulting XML document. When the ELMENTS XSINIL combination is present, an element outputs using an explicit xsi:nil syntax such as < addr:Region xsi:nil="true" / >. When you run the example, SQL 2005 outputs an XML document like the one that follows. Note the namespaces and prefixes are just as you defined them. < Customers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:addr="urn:hanselman.com/northwind/address" xmlns="urn:hanselman.com/northwind" > < Customer ID="ALFKI" > < CompanyName > Alfreds Futterkiste < /CompanyName > < addr:Address > < addr:Street > Obere Str. 57 < /addr:Street > < addr:City > Berlin < /addr:City > < addr:Region xsi:nil="true" / > < addr:Zip > 12209 < /addr:Zip > < addr:Country > Germany < /addr:Country > < /addr:Address > < Contact > < Name > Maria Anders < /Name > < Title > Sales Representative < /Title > < Phone > 030-0074321 < /Phone > < Fax > 030-0076545 < /Fax > < /Contact > < /Customer > the rest of the document removed for brevity The resulting XML can now be manipulated using an XmlReader or any of the techniques discussed in this chapter. Adding a Column of Untyped XML SQL Server can produce XML from a query, and it can now also store XML in a single column. Because XML is a first-class data type within SQL Server 2005, adding a new column to the Customers table of the Northwind Database is straightforward. You can use any SQL Server management tool you like. I use the SQL Server Management Studio Express, a free download that can be used with any SQL SKU (including the free SQL Express 2005). Bring up your Query Analyzer or Management Studio Express and, with the Northwind database selected, execute the following query: use Northwind; BEGIN TRANSACTION GO ALTER TABLE dbo.Customers ADD Notes xml NULL GO COMMIT 551 Evjen c10.tex V2 - 01/28/2008 2:13pm Page 552 Chapter 10: Working with XML and LINQ to XML Note the xml type keyword after Notes in the preceding example. If an XML Schema were already added to the database, you could add this new column and associate it with a named Schema Collection all at once using this syntax. use Northwind; BEGIN TRANSACTION GO ALTER TABLE dbo.Customers ADD Notes xml(DOCUMENT dbo.NorthwindCollection) GO COMMIT Here, the word DOCUMENT indicates that the column will contain a complete XML document. Use CONTENT to store fragments of XML that don’t contain a root note. You haven’t added a schema yet, so that’s the nextstep.Sofar,you’veaddeda Notes column to the Customers table that can be populated with prose. For example, a customer service representative could use it to describe interactions she’s had with the customer, entering text into a theoretical management system. Adding an XML Schema Although the user could store untyped XML data in the Notes field, you should really include some constraints on what’s allowed. XML data can be stored typed or untyped, as a fragment with no root note or as a document. Because you want store Customer interaction data entered and viewed from a Web site, ostensibly containing prose, XHTML is a good choice. XML data is validated by XML Schemas, as discussed earlier in the chapter. However, SQL Server 2005 is a database, not a file system. It needs to store the schemas you want to reference in a location it can get to. You add a schema or schemas to SQL Server 2005 using queries formed like this: CREATE XML SCHEMA COLLECTION YourCollection AS ’your complete xml schema here’ You’ll be using the XHTML 1.0 Strict schema located on the W3C Web site shown here: http://w3.org/TR/xhtml1-schema/#xhtml1-strict . Copy the entire schema to a file, or download the schema directly from http://w3.org/2002/08/xhtml/xhtml1-strict.xsd . When executing your query, you include the entire XSD inline in your schema. However, you should watch for few things. First, escape any single quotes so that ’ becomes ’’ — that is, two single quotes, not one double — using a search and replace. Second, because SQL 2005 uses the MSXML6 XML parser to parse its XML, take into consideration a limitation in that parser. MSXML6 already has the xml: names- pace prefix and associated namespace hard-coded internally, so you should remove the line from your schema that contains that namespace. This little oddity is documented, but buried within MSDN at http://msdn2.microsoft.com/ms177489(en-US,SQL.90).aspx and applies only to a few predefined schemas like this one that uses the xml: prefix and/or the http://www.w3.org/XML/1998/namespace namespace. In the fragment that follows, I’ve bolded the line you need to remove. Use Northwind; CREATE XML SCHEMA COLLECTION NorthwindCollection AS ’ < ?xml version="1.0" encoding="UTF-8"? > < xs:schema version="1.0" xml:lang="en" xmlns:xs="http://www.w3.org/2001/XMLSchema" targetNamespace="http://www.w3.org/1999/xhtml" 552 Evjen c10.tex V2 - 01/28/2008 2:13pm Page 553 Chapter 10: Working with XML and LINQ to XML xmlns="http://www.w3.org/1999/xhtml" xmlns:xml="http://www.w3.org/XML/1998/namespace" elementFormDefault="qualified" > the rest of the schema has been omitted for brevity < /xs:schema > ’; Instead, you want to execute a query like this, noting the single quote and semicolon at the very end. Use Northwind; CREATE XML SCHEMA COLLECTION NorthwindCollection AS ’ < ?xml version="1.0" encoding="UTF-8"? > < xs:schema version="1.0" xml:lang="en" xmlns:xs="http://www.w3.org/2001/XMLSchema" targetNamespace="http://www.w3.org/1999/xhtml" xmlns="http://www.w3.org/1999/xhtml" elementFormDefault="qualified" > the rest of the schema has been omitted for brevity < /xs:schema > ’; You may get a few schema validation warnings when you execute this query because of the complexity of the XHTML schema, but you can ignore them. Figure 10-12 shows the new NorthwindCollection schemas added to the Northwind database. Figure 10-12 553 Evjen c10.tex V2 - 01/28/2008 2:13pm Page 554 Chapter 10: Working with XML and LINQ to XML Although Figure 10-12 shows the NorthwindCollection within the Object Explorer, you can also confirm that your schema has been added correctly using SQL, as shown in the example that follows: Use Northwind; SELECT XSN.name FROM sys.xml_schema_collections XSC JOIN sys.xml_schema_namespaces XSN ON (XSC.xml_collection_id = XSN.xml_collection_id) The output of the query is something like the following. You can see that the XHTML namespace appears at the end along with the schemas that already existed in the system. http://www.w3.org/2001/XMLSchema http://schemas.microsoft.com/sqlserver/2004/sqltypes http://www.w3.org/XML/1998/namespace http://www.w3.org/1999/xhtml Next you should associate the new column with the new schema collection. Using the Management Studio, you create one composite script that automates this process. In this case, however, you can continue to take it step by step so you see what’s happening underneath. Associating a XML Typed Column with a Schema You can use the Microsoft SQLServer Management Studio Express to associate the NorthwindCollection with the new Notes column. Open the Customers table of the Northwind Database and, within its Col- umn collection, right-click and select Modify. Select the Notes column, as shown in Figure 10-13. Within the Notes column’s property page, open the XML Type Specification property and select the Northwind- Collection from the Schema Collection dropdown. Also, set the Is XML Document property to Yes . At this point, you can save your table and a change script is generated and executed. If you want to see and save the change script after making a modification but before saving the changes, right-click in the grid and select Generate Change Script. Click the Save toolbar button or Ctrl+S to commit your changes to the Customers table. Now that you’ve added a new Notes column and associated it with an XHTML schema, you’re ready to add some data to an existing row. Inserting XML Data into an XML Column You start by adding some data to a row within the Northwind database’s Customer table. Add some notes to the famous first row, the customer named Alfreds Futterkiste, specifically CustomerID ALFKI. You add or update data to a column with the XML type just as you add to any data type. For example, you can try an UPDATE query. Use Northwind; UPDATE Customers SET Notes = N’ < HTML >< /HTML > ’ WHERE CustomerID = ’ALFKI’; Upon executing this query, you get this result: Msg 6913, Level 16, State 1, Line 1 XML Validation: Declaration not found for element ’HTML’. Location: /*:HTML[1] 554 Evjen c10.tex V2 - 01/28/2008 2:13pm Page 555 Chapter 10: Working with XML and LINQ to XML What’s this? Oh, yes, you associated a schema with the XML column so the included document must conform, in this case, to the XHTML specification. Now, try again with a valid XHTML document that includes a correct namespace. Use Northwind; UPDATE Customers SET Notes = N’ < html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" > < head > < title > Notes about Alfreds < /title > < /head > < body > < p > He is a nice enough fellow. < /p > < /body > < /html > ’ WHERE CustomerID = ’ALFKI’; Figure 10-13 Execute this corrected query and you see a success message. (1 row(s) affected) After you’ve typed a column as XML and associated an XML Schema, SQL Server 2005 will allow only XML documents that validate. The data can be retrieved from SQL Server using standard System.Data 555 Evjen c10.tex V2 - 01/28/2008 2:13pm Page 556 Chapter 10: Working with XML and LINQ to XML techniques. It can be pulled out of a DataReader or a DataSet and manipulated with XmlReader sorasan XmlDocument . Summary XML and the XML InfoSet are both pervasive in the .NET Framework and in ASP.NET. All ASP.NET configuration files include associated XML Schema, and the Visual Studio Editor is even smarter about XML documents that use XSDs. XmlReader and XmlWriter provide unique and incredibly fast ways to consume and create XML; they now also include even better support for mapping XML Schema types to CLR types, as well as other improvements. The XmlDocument and XPathDocument return in .NET 2.0 with API additions and numer- ous performance improvements, while the XmlDataDocument straddles the world of System.Data and System.Xml . ASP.NET and .NET 3.5 include support for XSLT via not only the new XslCompiled- Transform but also command-line compilation, and tops it all with XSLT debugging support for com- piled stylesheets. LINQ to XML introduces the new System.Xml.Linq namespace and supporting classes for a tightly integrated IntelliSense-supported coding experience. VB9 takes XML support to the next level with XML literals and XML namespace imports. The bridge classes and extension methods make the transition between System.Xml and System.Xml.Linq clean and intuitive. All these ways to manipulate XML via the Base Class Library are married with XML support in SQL Server 2000 and 2005. SQL Server 2005 also includes the XML data type for storing XML in a first class column type validated by XML Schemas stored in the database. 556 . with XmlReader sorasan XmlDocument . Summary XML and the XML InfoSet are both pervasive in the .NET Framework and in ASP. NET. All ASP. NET configuration files include associated XML Schema, and the Visual Studio. Server using standard System.Data 55 5 Evjen c10.tex V2 - 01/28/2008 2:13pm Page 55 6 Chapter 10: Working with XML and LINQ to XML techniques. It can be pulled out of a DataReader or a DataSet and manipulated. ELEMENTS XSINIL 55 0 Evjen c10.tex V2 - 01/28/2008 2:13pm Page 55 1 Chapter 10: Working with XML and LINQ to XML The aliases using the AS keyword declaratively describe the elements and their nesting