9/08/2012

ASP.NET MVC – PartialView with Ajax

In this post I will briefly discuss how to use a PartialView either as a Web Form’s “UserControl” or as an“UpdatePanel”. You can click on the links to navigate accordingly, or just read on. UPDATE: If you’d rather have a “real world” example, you can read this post: http://evolpin.wordpress.com/2011/04/26/asp-net-mvc-partial-view-and-ajax-real-world-example/.
I’ve wanted to use a PartialView for quite some time now. A PartialView provides two functionalities which aren’t necessarily overlapping although have much in common. In comparison with Web Forms, a PartialView is a kind of a UserControl, but it also provides a kind of UpdatePanel.
What’s great about a PartialView is that it’s very straight forward – you simply place your HTML and/or JavaScript in a separate View file, probably placed in your Shared views folder so it can be used easily across views. Then you can either use it as a control, by placing Html.Partial(…) calls on your View as desired, or you can render it in run-time as a response to an Ajax call.
UserControl like implementation:
Let’s go quickly over the first usage, that of a “UserControl”: we right-click on the Shared folder to add a new View, give it a name, and select the “Create as a partial view” checkbox:
Our solution explorer looks as follows (Index.cshtml will be using the newly created MyPartialView.cshtml):
MyPartialView contains a simple dummy html place holder which will display a “My View” header; whereas Index.cshtml in this example will now include 2 references to MyPartialView:
1: <table border='1'> 
2:     <tr>
3:         <td>
4:             @Html.Partial("MyPartialView")
5:         </td>
6:         <td>
7:             @Html.Partial("MyPartialView")
8:         </td>
9:     </tr>
10: </table>
That’s about it, and this is how it looks:
One more thing worth mentioning, is that the ViewData dictionary which can be populated in the Controller, is available both to the View and to it’s Partial Views.
Ajax and html injection – “UpdatePanel”:
As I mentioned earlier, Partial Views can also provide a functionality resembling Web Form’s UpdatePanel. Those familiar with UpdatePanel usually either appreciate the (relatively) ease of use, or despise it for being so heavy and inefficient. Almost an Ajax “wannabe”. Personally, I think that UpdatePanel has its place in Ajax side by side with PageMethods/WebMethods. I consider it the better alternative for rendering a “mass” of html onto the client, especially when paging or sorting grids. In MVC, a PartialView could perform this exact functionality, and could prove to be the better solution for rendering plenty of html to the client.
Basically there are 4 steps to achieve this:
  1. Create a PartialView.
  2. Create a place holder html control.
  3. Use jQuery’s load method to fetch the partial view from the server and inject it into the place holder.
  4. Create a server side Action in a Controller that will return the partial view.
Here’s a quick example how this can be done. We’ll use the same partial view created earlier (i.e. MyPartialView.cshtml). So now we have to prepare a place holder div, and use jQuery to load and inject the response:
1: <a href='javascript:getView();'>Get Partial View</a>
2: <script type="text/javascript">
3:     function getView() {
4:         $('#divResult').load("@Url.Action("GetView" , "Home" )");
5:     }
6: </script>
7: <div id='divResult'>
8: </div>
  • Line 1 is a simple anchor which will invoke the JavaScript containing jQuery’s load.
  • Line 4 is the simple one-line code which performs an ajax call to the server’s GetView action in the Home Controller (will be done shortly), and injects the result.
  • Line 7-8 is where the result will be injected to.
And now for the server side – pretty self-explanatory:
1: public ActionResult GetView()
2: {
3:    return PartialView("MyPartialView");
4: }
When we run the sample, prior to the Ajax call this looks like this:
After clicking the link, jQuery’s load method performs the Ajax call as expected, and the partial view’s HTML is injected into the place holder. All this can be viewed in the picture below:
  1. The load method performs a GET operation to the server’s GetView.
  2. Response is returned with html.
  3. jQuery injects the result in the place holder div.
  4. The result is rendered in the browser.
That’s it! No doubt this is real simple to achieve. From here on, the possibilities are quite remarkable. You see, it turns out that when jQuery injects the code, it is also able to inject JavaScript. This means that you can actually render not only html, but JavaScript code as well. Naturally you can argue if this is a good thing or not, but it just gives you a hint on how extensible this can be.
As usual, credits are in order. This great post summarizes different jQuery Ajax approaches with ASP.NET MVC.
BTW: If you ask yourself why you should use jQuery instead of Microsoft Client libraries, I guess you should relate to Microsoft’s statements about “throwing its weight behind jQuery”. You can read about this in Stephen Walther’s blog. Stephen’s conclusion says it all: “Our plan is to focus on jQuery as the primary technology for building client-side Ajax applications moving forward. We want to adapt Microsoft technologies to work great with jQuery and we want to contribute features to jQuery that will make the web better for everyone. We are very excited to be working with the jQuery core team.” Although MVC 3 comes with Microsoft Ajax client scripts, it also includes jQuery and I guess that we’ll see more and more of jQuery in Microsoft’s VS templates.

9/04/2012

Converting String Data to XML and XML to String Data


01 February 2012
We all appreciate that, in general, XML documents or fragments are held in strings as text markup. In SQL Server, XML variables and columns are instead tokenised to allow rapid access to the data within. This is fine, but can cause some odd problems, auch as 'entitization'. What, also, do you do if you need to preserve the formatting? As usual Rob Sheldon comes to our aid.
When you’re working with XML data, you might find that you want to convert a value from the XML data type to another type, or from another type to the XML type. SQL Server lets you do both, to a limited degree. You can convert XML data to any of the string or binary types, and you can convert any of the string or binary types to XML. The process you follow if fairly straightforward in either case. Mostly, you need to know when SQL Server will do implicit conversions or when you must explicitly cast data into another type. And you should be aware of a couple other subtleties when converting to or from XML data.
NOTE: This article is the fourth in a series about working with XML data in SQL Server. The first three articles cover the XML data type (“Working with the XML Data Type in SQL Server”), its methods (“The XML Methods in SQL Server”), and incorporating XML into database objects (“Incorporating XML into Your Database Objects”).

Converting String Data to XML

In SQL Server, you can convert data configured with any of the character or binary data types—such as CHAR, VARCHAR, and VARBINARY—to the XML data type. You can use the CAST() or CONVERT() function to explicitly cast the data to a different type, or you can let SQL Server implicitly convert the data. For instance, SQL Server will automatically convert a string value stored with the NVARCHAR data type into an XML value, as shown in the following example:
DECLARE @string NVARCHAR(MAX);DECLARE @xml XML;SET @string =
'<bookstore><book>Candide</book></bookstore>';SET @xml = @string;SELECT @xml;
In this set of statements, I first declare the @string variable with the NVARCHAR(MAX) data type and then the @xml variable with the XML data type. I assign a string value—an XML fragment—to the @string data type, then set the value of @xml to equal @string. Because SQL Server can implicitly convert an NVARCHAR value to an XML value, the assignment is very straightforward, which I confirm by querying the @xml variable. As expected, the SELECT statement returns the following results:
<bookstore><book>Candide</book></bookstore>
The result set contains the XML fragment as it was originally assigned to the @string variable. The same holds true for other character data types, in terms of performing implicit conversions. For instance, in the following example, I replace the NVARCHAR(MAX) data type with VARCHAR(100):
DECLARE @string VARCHAR(100);DECLARE @xml XML;SET @string =
'<bookstore><book>Candide</book></bookstore>';SET @xml = @string;SELECT @xml;
This example is identical to the preceding one, except for switching the character data types. As a result, the SELECT statement returns the same XML fragment as before:
<bookstore><book>Candide</book></bookstore>
Again, SQL Server has implicitly converted the string data to XML. However, you can explicitly convert that data if you want. In the following example, I use the CAST() function to convert the @string value:
DECLARE @string VARCHAR(100);DECLARE @xml XML;SET @string =
'<bookstore><book>Candide</book></bookstore>';SET @xml = CAST(@string AS XML);SELECT @xml;
When you specify the CAST() function, you must pass in an expression that references the data to be converted, in this case, the @string variable, followed by the AS keyword and the name of the target data type, XML. All this is enclosed in parentheses, as I’ve done here. I then assign the value returned by the CAST() function to the @xml variable. As to be expected, the SELECT statement returns the same results as the previous examples:
<bookstore><book>Candide</book></bookstore>
You can just as easily use the CONVERT() function to achieve the same results. The main difference is how you structure the arguments that you pass into the function. For CONVERT(), you must first specify the target data type (XML), followed by the expression that references the source data (@string), as shown in the following example:
DECLARE @string VARCHAR(100);DECLARE @xml XML;SET @string =
'<bookstore><book>Candide</book></bookstore>';SET @xml = CONVERT(XML, @string);SELECT @xml;
Notice that I follow the XML data type argument with a comma, then I specify the @string variable. Once again, the SELECT statement returns the XML fragment as it is assigned to the @string variable:
<bookstore><book>Candide</book></bookstore>
You might be wondering why you would explicitly cast string or binary data to the XML type if SQL Server handles such conversions implicitly. Let’s look at the CAST() function first. One reason you might want to use CAST()is if you plan to run your SQL script against a database management system in addition to SQL Server and that system doesn’t support implicit conversions. Because the CAST() function conforms to ANSI specifications, you can use it with any database systems that conform to those standards.
That’s not the case with the CONVERT() function, which is specific to Transact-SQL in SQL Server. If you want to run your script against another database system, you have to modify your code to conform to that system’s specifications. However, if you’re running your script only against SQL Server, you can take advantage of the CONVERT() function’s support for additional options, which let you better refine the conversion process.
For instance, in the following example I use tabs between the parent and child elements in the XML fragment:
DECLARE @string VARCHAR(100);DECLARE @xml XML;SET @string =
'<bookstore> <book>Candide</book> </bookstore>';SET @xml = CONVERT(XML, @string);SELECT @xml;
The tabs serve only to add white space between the XML elements and do not affect the elements themselves. Then, as I do in the previous example, I use the CONVERT() function to cast the string as XML data. However, when I run the SELECT statement, the data no longer includes the white space, as shown in the following results:
<bookstore><book>Candide</book></bookstore>
But I can preserve the white space in the original string by adding a third argument to the CONVERT() function, as shown in the following example:
DECLARE @string VARCHAR(100);DECLARE @xml XML;SET @string =
'<bookstore> <book>Candide</book> </bookstore>';SET @xml = CONVERT(XML, @string, 1);SELECT @xml;
Notice that the function’s third argument is 1, which tells SQL Server to preserve the white space in the source data during the conversion process. Now the SELECT statement returns the following results:
<bookstore><book>Candide</book></bookstore>
This time the tabs are intact. I could have used spaces instead and received similar results. Now let’s look at what happens when we add linefeeds to the XML fragment. In the following example, I modify the string to include an additional book along with linefeeds and tabs:
DECLARE @string VARCHAR(100);DECLARE @xml XML;SET @string = '<bookstore>
<book>Candide</book>
<book>Pride and Prejudice</book>
</bookstore>'
;SET @xml = CONVERT(XML, @string, 1);SELECT @xml;
Because I pass the third argument (1) into the CONVERT() function, as I did in the previous example, the conversion process preserves the white space and linefeeds, as shown in the results returned by the SELECT statement:
<bookstore>
<book>Candide</book>
<book>Pride and Prejudice</book>
</bookstore>
Another thing worth noting about converting data to the XML data type is that once you’ve converted the data, you can use the data type’s methods to retrieve data. For example, I modified the SELECT statement in the previous example to include the query() method:
DECLARE @string VARCHAR(100);DECLARE @xml XML;SET @string = '<bookstore>
<book>Candide</book>
<book>Pride and Prejudice</book>
</bookstore>'
;SET @xml = CONVERT(XML, @string, 1);SELECT @xml.query('/bookstore/book[2]');
As you can see, I pass in the second instance of the child element (/bookstore/book[2]) as an argument to the query() method. The SELECT statement now returns the following results.
<book>Pride and Prejudice</book>
You can, of course, use the query() method to perform more complex queries, and you can use other methods. The key is to save the string data to the XML type and then call the method.

Converting XML to String Data

Although SQL Server implicitly converts string and binary data to the XML data type, it doesn’t work the other way. All conversions from the XML type to string and binary types must be done explicitly using the CAST() or CONVERT() function. If you try to perform an implicit conversion, your statement will fail. For instance, the following example tries to convert the XML fragment to VARCHAR(100):
DECLARE @xml XML;DECLARE @string VARCHAR(100);SET @xml =
'<bookstore><book>Candide</book></bookstore>';SET @string = @xml;SELECT @string;
After I declare the @xml and @string variables, I assign the XML fragment to the @xml variable. I then try to set the @string variable to equal the @xml variable, but when I try to run these statements, SQL Server returns the following error:
Implicit conversion from data type xml to varchar is not allowed. Use the CONVERT function to run this query.
The problem is easy enough to fix. In the following example, I include the CAST() function to explicitly convert the XML value to VARCHAR(100):
DECLARE @xml XML;DECLARE @string VARCHAR(100);SET @xml =
'<bookstore><book>Candide</book></bookstore>';SET @string = CAST(@xml AS VARCHAR(100));SELECT @string;
Now the XML value is converted into a string with no problem. When I retrieve the value of @string, the SELECT statement returns the XML fragment, as shown in the following results:
<bookstore><book>Candide</book></bookstore>
I could have just as easily used the CONVERT() function to cast the XML value to VARCHAR:
DECLARE @xml XML;DECLARE @string VARCHAR(100);SET @xml =
'<bookstore><book>Candide</book></bookstore>';SET @string = CONVERT(VARCHAR(100), @xml);SELECT @string;
As to be expected, the conversion occurs without a hitch and the XML fragment is assigned to the @string variable, which the SELECT statement confirms. Now suppose you insert linefeeds and tabs into your XML fragment, as I do in the following example:
DECLARE @xml XML;DECLARE @string VARCHAR(100);SET @xml ='<bookstore>
<book>Candide</book>
<book>Pride and Prejudice</book>
</bookstore>'
;SET @string = CONVERT(VARCHAR(100), @xml, 1);SELECT @string;
Because the XML fragment contains these new elements, you might expect that you can simply add the third argument to the CONVERT() function to preserve the tabs and linefeeds. However, although I take this approach, the value saved to the @string variable does not preserve these elements, as shown in the results returned by the SELECT statement:
<bookstore><book>Candide</book><book>Pride and Prejudice</book></bookstore>
The problem is not with how I convert the @xml value, but with the way I assign the XML fragment to that variable. Notice that I assign the value simply by setting @xml to equal the XML fragment, enclosed in single quotes. What is essentially happening here is that SQL Server is implicitly converting a string value to the XML type, which means that the tabs and linefeeds are not being preserved during that assignment. The way to get around this is to use the CONVERT() function to explicitly cast the string to the XML type, as I do in the following example:
DECLARE @xml XML;DECLARE @string VARCHAR(100);SET @xml = CONVERT(XML,'<bookstore>
<book>Candide</book>
<book>Pride and Prejudice</book>
</bookstore>'
, 1);SET @string = CONVERT(VARCHAR(100), @xml, 1);SELECT @string;
As you can see, I include a third argument in the CONVERT() function that specifies these elements be preserved. The SELECT statement returns the following results:
<bookstore>
<book>Candide</book>
<book>Pride and Prejudice</book>
</bookstore>
Now the string includes the tabs and linefeeds as they exist in the original XML fragment. Let’s take a closer look at this process so you can better understand what’s happening behind the scenes when you convert XML data.

XML Entitization

As the last example demonstrates, you can use the CONVERT() function to preserve characters that the XML parser normally ignores, such as spaces and tabs between elements. What this points to is that SQL Server handles certain components of an XML fragment—whether converting to or from the XML type—differently from the basic elements, attributes, and their values.
To better understand what’s happening, take a look at another example. The following set of Transact-SQL statements are similar to the last example, except that I’ve included a second SELECT statement to retrieve the @xml value and I’ve modified the second instance of the CONVERT() function so it doesn’t include the third argument (1):
DECLARE @xml XML;DECLARE @string VARCHAR(100);SET @xml = CONVERT(XML,'<bookstore>
<book>Candide</book>
<book>Pride and Prejudice</book>
</bookstore>'
, 1);SELECT @xml;SET @string = CONVERT(VARCHAR(100), @xml);SELECT @string;
As you might expect, the new SELECT statement returns the XML fragment with the linefeeds and tabs in place:
<bookstore>
<book>Candide</book>
<book>Pride and Prejudice</book>
</bookstore>
However, the second SELECT statement returns several unexpected values:
<bookstore>
&#x09;<book>Candide</book>
&#x09;<book>Pride and Prejudice</book>&#x0A;</bookstore>
NOTE: If I were to try to run this example without converting the original XML fragment, or converting the fragment without specifying the third argument in the CONVERT() function, the tabs and linefeeds would not be preserved, and the two SELECT statements would return the string without those tabs and linefeeds.
When the XML parser stores characters such as tabs and linefeeds within an XML document or fragment, it must do so in a way that preserves them throughout the parser’s normalization process, allowing these elements to be maintained whether being stored or retrieved or whether being converted to or from XML. Tabs and linefeeds are just two of the special characters that need to be preserved in this way, as the above example demonstrates, with tabs being saved as &#x09; and linefeeds saved as &#x0A;. When you retrieve the XML value directly, these special characters are automatically displayed in a readable format.
The process of preserving certain characters with these special symbols is known as entitization. Unfortunately, the XML parser is not always consistent in entitizing characters, as demonstrated by the fact that only one linefeed is stored as &#x0A;. Another issue is the way the parser handles special characters that appear in an element or attribute value. In such cases, SQL Server simply returns an error, rather than trying to entitize them. For instance, in the following example I replace the word “and” in the second book title with an ampersand (&):
DECLARE @xml XML;DECLARE @string VARCHAR(100);SET @xml = CONVERT(XML,'<bookstore>
<book>Candide</book>
<book>Pride & Prejudice</book>
</bookstore>'
, 1);SELECT @xmlSET @string = CONVERT(VARCHAR(100), @xml);SELECT @string;
When I run this statement, I receive the following error:
XML parsing: line 3, character 15, illegal name character
The XML parser doesn’t like characters such as ampersands, left brackets (<), and right brackets (>) within element and attribute values. To store XML that contains these characters, you must manually entitize them by replacing the character with their symbols. In this case, I replace the ampersand with &amp;:
DECLARE @xml XML;DECLARE @string VARCHAR(100);SET @xml = CONVERT(XML,'<bookstore>
<book>Candide</book>
<book>Pride &amp; Prejudice</book>
</bookstore>'
, 1);SELECT @xml;SET @string = CONVERT(VARCHAR(100), @xml);SELECT @string;
The parser can now parse the XML fragment. In doing so, it preserves the entitized ampersand, as shown in the results returned by the first SELECT statement:
<bookstore>
<book>Candide</book>
<book>Pride &amp; Prejudice</book>
</bookstore>
As you can see, the second book title contains the entitized ampersand. Now SQL Server has no problem handling the element’s value when that value is stored, retrieved, or converted. The entitized ampersand is also preserved when you convert the XML value to a string, which the second SELECT statement confirms:
<bookstore>
&#x09;<book>Candide</book>
&#x09;<book>Pride &amp; Prejudice</book>&#x0A;</bookstore>
Of course, chances are pretty good you won’t want your string value to show the entitized characters. As before, you can try to get rid of them by adding the third argument to the second CONVERT() function, as shown in the following Transact-SQL:
DECLARE @xml XML;DECLARE @string VARCHAR(100);SET @xml = CONVERT(XML,'<bookstore>
<book>Candide</book>
<book>Pride &amp; Prejudice</book>
</bookstore>'
, 1);SELECT @xml;SET @string = CONVERT(VARCHAR(100), @xml, 1);SELECT @string;
The second SELECT statement now returns the following results:
<bookstore>
<book>Candide</book>
<book>Pride &amp; Prejudice</book>
</bookstore>
Almost there. The entitized tab and linefeed characters are returned as regular characters, but the ampersand within the element value is still entitized. To address this issue, you have to specifically remove it from your string. For example, in the following Transact-SQL I use the REPLACE() function to replace the &amp; value with an ampersand:
DECLARE @xml XML;DECLARE @string VARCHAR(100);SET @xml = CONVERT(XML,'<bookstore>
<book>Candide</book>
<book>Pride &amp; Prejudice</book>
</bookstore>'
, 1);SELECT @xml;SET @string =
REPLACE(CONVERT(VARCHAR(100), @xml, 1), '&amp;', '&');SELECT @string;
For the first argument of the REPLACE() function, I pass in the converted @xml value, then specify the value to be replaced (&amp;) and the new value (&). Now the second SELECT statement returns the value we want:
<bookstore>
<book>Candide</book>
<book>Pride & Prejudice</book>
</bookstore>
As the results show, the string value no longer contains any entitized characters. As you work with XML data, you’ll get a better feel for when to manually entitize and de-entitize data. For more information about the entitization process in SQL Server, see the topic “Serialization of XML Data” in SQL Server Books Online.

XML Data Conversion

For the most part, converting data from the XML data type to another type or converting it from another type to the XML type is a fairly painless process. Not surprisingly, there are a few gotchyas along the way, entitization being one of them. For more details about converting XML data, see the topics “Generating XML Instances” and “CAST and CONVERT (Transact-SQL)” in SQL Server Books Online. Also worth checking out is how to incorporate the FOR XML clause in a SELECT statement to return results as XML. You can find details about that in my Simple-Talk article “Using the FOR XML Clause to Return Query Results as XML.” As you can see, SQL Server provides a number of ways to work with XML data. This article, along with the first three in the series, should have provided you with a good overview of the various considerations to take into account when working with XML. 

Recommendations - NVARCHAR


Recommendations

Don’t Use VARCHAR(MAX) Everywhere

One design-simplification technique that has been suggested is to use VARCHAR(MAX) for every string column. Though the tests here show that using VARCHAR(MAX) (in-row) instead of VARCHAR(n) is not a performance problem for strings under 8,000 characters, there are a number of reasons to limit the length of strings in your database.

UI Issues

Anyone with experience creating an application UI knows that handling long strings is difficult. When laying out the UI for an application, it is important to know the expected and maximum length of strings that will be displayed. Leaving this open to whatever someone writes to the database makes designing, building, and testing an application very difficult.

Performance – Writing or Updating Long Strings

From the performance results, you can see that writing long strings definitely affects write delays. While writing a record of any size (even zero characters) takes time, writing longer strings takes more time. As an example, using VARCHAR(n), writing 1,000 characters takes an average of 217 microseconds while writing 8,000 characters takes an average of 448 microseconds.

Compression

What I believe is the most compelling reason to avoid over-8,000-character strings is compression. LOB data never gets compressed.
“When considering data compression one very key thing to remember is that out of row data (LOB data) isn’t compressed. If the LOB data is stored in row, then it will be compressed, but only when you compress the table with PAGE level compression. If however you use ROW level compression then the LOB data will never be compressed, no matter if it is stored in row or if it is stored out of row.” (ref)

Incorporating XML into your Database Objects


04 January 2012
XML data can become a full participant in a SQL Server Database, and can be used in views, functions, check constraints, computed columns and defaults. Views and table-valued functions can be used to provide a tabular view of XML data that can be used in SQL Expressions. Robert Sheldon explains how.
In my last two articles, “Working with the XML Data Type in SQL Server” and “The XML Methods in SQL Server,” I discussed how to use the XML data type and its methods to store and access XML data. This article takes these topics a step further and explains ways in which you can implement XML within various database objects, including views, functions, computed columns, check constraints, and defaults. If you’re not familiar with how the XML data type and its methods are implemented in SQL Server, you should review the first two articles before starting in on this one. Once you have the basic information, you’ll find that incorporating XML within these other database objects is a relatively straightforward process.

Creating Views

When you create a view, you can include an XML column just like you would a column configured with another data type. For example, the Sales.Store table in the AdventureWorks2008R2 sample database includes the Demographics column, which is configured with the XML type. For each row in the table, the column stores an XML document that contains details about the store described in that row of data.
In the following example, I create a view that retrieves data from the Sales.Store table, including the Demographics column:
USE AdventureWorks2008R2;
IF OBJECT_ID('StoreSurvey') IS NOT NULLDROP VIEW StoreSurvey;GO

CREATE VIEW StoreSurvey
AS
SELECT
BusinessEntityID AS StoreID,
Demographics AS Survey
FROM
Sales.Store;GO

SELECT Survey
FROM StoreSurvey
WHERE StoreID = 292;
There should be no surprises here. I simply specified the name of the XML column as I would other columns and assigned the alias Survey to the column name. The SELECT statement I tagged onto the view definition returns the following results:
<StoreSurvey xmlns="http://schemas.microsoft.com/sqlserver/2004/07/adventure-works/StoreSurvey"
<AnnualSales>800000</AnnualSales>
<
AnnualRevenue>80000</AnnualRevenue>
<
BankName>United Security</BankName>
<
BusinessType>BM</BusinessType>
<
YearOpened>1996</YearOpened>
<
Specialty>Mountain</Specialty>
<
SquareFeet>21000</SquareFeet>
<
Brands>2</Brands>
<
Internet>ISDN</Internet>
<
NumberEmployees>13</NumberEmployees>
</
StoreSurvey>
The SELECT statement returns the full XML document, as it’s stored in the Demographics column (for the row with a StoreID value of 292). Although this all fine enough, it’s not particularly noteworthy. Where things get interesting is when you use an XML method within a view to retrieve only part of the XML data. For example, in the following view definition, I use the value() method to retrieve a single value from the XML document in the Demographics column:
USE AdventureWorks2008R2;
IF OBJECT_ID('StoreSales') IS NOT NULLDROP VIEW StoreSales;GO

CREATE VIEW StoreSales
AS
SELECT
BusinessEntityID AS StoreID,
Demographics.value('declare namespace ns=
"http://schemas.microsoft.com/sqlserver/2004/07/adventure-works/StoreSurvey";
(/ns:StoreSurvey/ns:AnnualSales)[1]'
, 'int') AS AnnualSales
FROM
Sales.Store;GO

SELECT AnnualSales
FROM StoreSales
WHERE StoreID = 292;
The value() method retrieves the value stored in the <AnnualSales> child element of the <StoreSurvey> element. Because the Demographics column is a typed XML column, the value() method’s first argument includes the namespace reference, along with a reference to the actual element. And the method’s second argument specifies that the value be returned as type int. The SELECT statement I’ve tagged onto the view definition now returns only a single value, 800000.
Because XQuery expressions can get somewhat complex, particularly when they include the namespace reference, views provide a handy way of storing those expressions in your database. For instance, if you want to retrieve annual sales information regularly, a view such as the one above can make the process quite easy.

Creating Functions

As is the case with views, you can also work with XML data within functions. For example, in the following function definition, I return the contents of the Demographics column based on an inputted store ID value (which is equivalent to the table’s BusinessEntityID value):
USE AdventureWorks2008R2;
IF OBJECT_ID('Survey') IS NOT NULLDROP FUNCTION Survey;GO

CREATE FUNCTION Survey(@StoreID INT)RETURNS XML
AS BEGIN
RETURN
(
SELECT Demographics
FROM Sales.Store
WHERE BusinessEntityID = @StoreID)END;GO

SELECT dbo.Survey(292);
Once again, no surprises here. When I call the function and store ID (292) in a SELECT statement, the function returns the full XML document from the Demographics column for that store. However, notice that I specified XML as the return type. Because XML is being returned, you can even use the XML methods when you call the function. For instance, in the following example, I again call the Survey function, passing in the store ID (292), but this time I also include the value() method:
SELECT dbo.Survey(292).value('declare namespace ns=
"http://schemas.microsoft.com/sqlserver/2004/07/adventure-works/StoreSurvey";
(/ns:StoreSurvey/ns:AnnualSales)[1]'
, 'int');
Notice that I simply add the period and method name when I call the function, followed by the necessary arguments. As you would expect, the function now returns only a single value, in this case, 800000.
I could have just as easily specified the query() method, as in the following example:
SELECT dbo.Survey(292).query('declare namespace ns=
"http://schemas.microsoft.com/sqlserver/2004/07/adventure-works/StoreSurvey";
/ns:StoreSurvey/ns:AnnualSales'
);
Now the SELECT statement returns the entire <AnnualSales> element, along with the namespace information, as shown in the following results:
<ns:AnnualSales xmlns:ns="http://schemas.microsoft.com/sqlserver/2004/07/adventure-works/StoreSurvey">800000</ns:AnnualSales>
So far, working with XML data within a function has been pretty straightforward, but suppose you don’t want to retrieve the entire XML document. You can instead use XML methods within the function to return specific information from the document. In the following example, I create a function that returns only the value from the <AnnualSales> child element, just like I did earlier in my view definition:
USE AdventureWorks2008R2;
IF OBJECT_ID('AnnualSales') IS NOT NULLDROP FUNCTION AnnualSales;GO

CREATE FUNCTION AnnualSales(@survey XML)RETURNS INT
AS BEGIN
RETURN
@survey.value('declare namespace ns=
"http://schemas.microsoft.com/sqlserver/2004/07/adventure-works/StoreSurvey";
(/ns:StoreSurvey/ns:AnnualSales)[1]'
, 'int')END;GO

SELECT dbo.AnnualSales(Demographics) AS AnnualSales
FROM Sales.Store
WHERE BusinessEntityID = 292;
There are a couple things worth noting about this function definition. First, unlike the preceding example, I specified INT as the return type (rather than XML) because INT is the value type returned by the value() method. In addition, I specified XML as the type for the input parameter (@survey). I then used that parameter to call the value() method. That way, I can pass in the column name as the argument to the function, and the value() method will return the specific value from the XML document in the column.
To verify this, I created a SELECT statement that calls the function and passes in the Demographics column as its input argument. Now the function returns a value of 800000, without having to specify an XML method when calling the function itself, as in the preceding example.
Being able to use XQuery expressions within functions provides the same advantages as being able to use them in views. You can save complex expressions to the database that you can call as often as necessary, without having to reconstruct those expressions each time you need them.

Creating Computed Columns

There might be times when you want to use XML data to construct a computed column. One simple way to do that is to convert an entire XML value to a string, as I do in the following example:
USE AdventureWorks2008R2;
IF OBJECT_ID('Stores') IS NOT NULLDROP TABLE Stores;GO

CREATE TABLE Stores
(
StoreID INT PRIMARY KEY,
SurveyXml XML,
SurveyChar AS CAST(SurveyXml AS NVARCHAR(MAX))
);
GO

INSERT INTO Stores(StoreID, SurveyXml)SELECT BusinessEntityID, Demographics
FROM Sales.Store;
SELECT * FROM Stores
WHERE StoreID = 292;
First, I create the Stores table, which includes one XML column and one calculated column. The calculated column is simply a recasting of the XML column to an NVARCHAR(MAX) column. Notice in the INSERT statement, I retrieve data from the Sales.Store column, including the Demographics column. The Demographics data is converted to NVARCHAR(MAX) when inserted into the SURVEYCHAR column. As you would expect, the SELECT statement returns the full XML document for the SurveyXml column and returns the same document as character data for the SurveyChar column.
This is all well and good if all you want to do is play around with the entire XML document. But suppose instead you want to create a calculated column based on only a portion of that document. That’s where things get a bit sticky.
It turns out that you can’t use XML methods to define a calculated column. However, there is a work-around. You can define a function that retrieves the data you need and then use that function within the column definition, as I do in the following example:
USE AdventureWorks2008R2;
IF OBJECT_ID('Stores') IS NOT NULLDROP TABLE Stores;GO

IF OBJECT_ID('AnnualSales') IS NOT NULLDROP FUNCTION AnnualSales;GO

CREATE FUNCTION AnnualSales(@survey XML)RETURNS INT
AS BEGIN
RETURN
@survey.value('declare namespace ns=
"http://schemas.microsoft.com/sqlserver/2004/07/adventure-works/StoreSurvey";
(/ns:StoreSurvey/ns:AnnualSales)[1]'
, 'int')END;GO

CREATE TABLE Stores
(
StoreID INT PRIMARY KEY,
Survey XML,
SalesAmount AS dbo.AnnualSales(Survey)
);
GO

INSERT INTO Stores(StoreID, Survey)SELECT BusinessEntityID, Demographics
FROM Sales.Store;
SELECT * FROM Stores
WHERE StoreID = 292;
First, I create a function (AnnualSales) that uses the value() method to retrieve the <AnnualSales> value from the inputted XML document, as you saw in a previous example. I then create a table that includes an XML column (Survey) and a calculated column (SalesAmount). Within the SalesAmount column definition, I call the AnnualSales function and pass in the Survey column as its argument. Next, I populate the table with data from the Sales.Store table and then use a SELECT statement to retrieve a row from that table. As to be expected, the Survey column returns the full XML document, and the SalesAmount column returns a value of 800000.
This is, of course, a roundabout way to use XML methods to create a calculated column, but it’s an effective approach nonetheless. The key is making sure that the function targets the XML data that will provide the source for the calculated column. That means any schema and element references must be consistent with the source data.

Creating Check Constraints

Another way you can work with XML in your database is to create check constraints on an XML column. However, chances are, if you decide to do that, you’ll want to use one of the XML methods to create the constraint. The problem with this is that, like calculated columns, you can’t use these methods within the constraint definition. Once again, you must first create a function, as I’ve done in the following example:
USE AdventureWorks2008R2;
IF OBJECT_ID('Stores') IS NOT NULLDROP TABLE Stores;GO

IF OBJECT_ID('SpecialtyExists') IS NOT NULLDROP FUNCTION SpecialtyExists;GO

CREATE FUNCTION SpecialtyExists(@survey XML)RETURNS BIT
AS BEGIN
RETURN
@survey.exist('declare namespace ns=
"http://schemas.microsoft.com/sqlserver/2004/07/adventure-works/StoreSurvey";
/ns:StoreSurvey/ns:Specialty'
)END;GO

CREATE TABLE Stores
(
StoreID INT PRIMARY KEY,
Survey XML CHECK(dbo.SpecialtyExists(Survey) = 1)
);
GO

INSERT INTO Stores(StoreID, Survey)SELECT BusinessEntityID, Demographics
FROM Sales.Store;
SELECT * FROM Stores
WHERE StoreID = 292;
In this case, the function (SpecialtyExists) uses the exist() method to determine whether the inputted XML document contains the <Specialty> child element within the <StoreSurvey> parent element. The EXIST() method will return 1 if the child element exists within the parent element and will return a 0 if the two elements don’t exist as specified. Consequently, the return type used for the function is BIT. As for the EXIST() method itself, it takes only one argument, the namespace reference and the parent and child elements.
After I defined the function, I created a table (Stores), which includes an XML column (Survey). I’ve also defined a check constraint on the column. The constraint uses the SpecialtyExists function to determine if the data to be inserted into the Survey column contains the specified child and parent elements. If the data contains the elements, the rows are inserted into the table; otherwise the insertion fails.
For instance, when I inserted the data from the Sales.Store table, the Demographics data was added to the store data because that data includes the required child and parent elements. However, if the function had specified a different child element, such as <MainType>, no rows would have been inserted because the check constraint would have evaluated to False in each case.
As you saw with calculated columns, functions provide a workaround for using XML methods to create check constraints on XML columns. In the example above, I used the EXIST() method to verify the existence of an element. However, I could have also used that method to check the existence of an attribute, or I could have used another XML method, although the EXIST() method will probably prove to be the handiest one in this case.

Creating Defaults

If you create a table that includes an XML column, you can create a DEFAULT constraint on that column, as you would other column types. The main difference with the XML column, however, is that if that column is typed XML, your constraint can’t violate the schema associated with that column.
You can create a DEFAULT constraint on an XML column in one of two ways: by implicitly converting the string value to XML or explicitly converting the value. In the following example, I use both approaches:
USE AdventureWorks2008R2;
IF OBJECT_ID('Stores') IS NOT NULLDROP TABLE Stores;GO

CREATE TABLE Stores
(
StoreID INT PRIMARY KEY,
Survey_implicit1 XML
DEFAULT
'<Survey>Survey to follow</Survey>',
Survey_implicit2 XML
DEFAULT
'<Survey>Survey to follow</Survey>',
Survey_explicit1 XML
DEFAULT
CAST('<Survey>Survey to follow</Survey>' AS XML),
Survey_explicit2 XML
DEFAULT
CAST('<Survey>Survey to follow</Survey>' AS XML)
);
GO

INSERT INTO Stores
(StoreID, Survey_implicit1, Survey_explicit1)SELECT BusinessEntityID, Demographics, Demographics
FROM Sales.Store;
SELECT * FROM Stores
WHERE StoreID = 292;
Notice that I created four XML columns in the table. I did this to verify how the defaults work when I don’t add data to the columns. The first two XML columns (Survey_implicit1 and Survey_implicit2) each include default definitions that specify an XML value as a string without trying to convert that value to the XML type. For the third and fourth columns (Survey_explicit1 and Survey_explicit2), I specifically cast the columns to the XML type. Because SQL Server automatically converts the string values, you can take either approach.
After I defined the table, I inserted data into the Survey_implicit1 and Survey_explicit1 columns. I then retrieved a row from the table. As to be expected, the Survey_implicit1 and Survey_explicit1 columns returned the entire XML documents and the Survey_implicit2 and Survey_explicit2 columns each returned the value defined in the DEFAULT constraint.

Working with XML

As you saw with the DEFAULT constraints shown in the example above, you can convert non-XML data into XML. You can also convert XML data into other types. In my next article, I’ll provide more specifics on how those conversions work. In the meantime, this article should have provided you with the details you need to work with XML data when creating views, functions, computed columns, check constraints, and defaults. Of course, the XML methods, and the XQuery expressions you can create by using those methods, often play an important role in how you incorporate XML in your database objects, so knowing those methods and the XQuery language can be pivotal in effectively using that XML.