Packt Publishing has 2 new books titled:
- ASP.NET Data Presentation Controls Essentials
- Beginners Guide to SQL Server Integration Services Using Visual Studio 2005
Introduction
LINQ to XSD enhances XML programming by adding the feature of typed views on un-typed XML trees. A similar type of feature is available for DataSets in ADO. NET programming where we have typed DataSets. LINQ to XSD gives a better programming environment by providing the object models generated from XML schemas. This is called typed XML programming.
LINQ to XSD is an incubation project on typed XML programming. This product is not released yet. All examples and information in this chapter are based on this incubation project and are tested with Visual Studio 2008 Beta 1.
This LINQ to XSD project should reference System.Xml.XLinq
and Microsoft.Xml. Schema.Linq
libraries. Following is an example of accessing un-typed XML elements using LINQ query.
from c in LoadIcecreams.Elements("Icecream")
select new XElement("Icecream",
c.Element("Price"),
c.Element("Name")));
An equivalent LINQ query for the above un-typed XML as a typed XML would be as follows:
from Icecream in chapter6.Icecream
select new {Icecream.Price, Icecream.Name};
In this chapter we will see how to create typed XML, the features supported by typed XML, and how it helps in development.
The XML element that has been assigned a data type in an XML schema is called typed XML. This data type is used by the XML parser to validate the XML element value against the data type. The data type definition resides either in the same XML file, or in a separate schema file.
Let us consider the following XML in all our examples. It contains a namespace called http://www.Sample.com/Items. The XML has details of three different ice-creams. The root element of the XML is Chapter6
. The first line in the XML shows details like version, and encoding for the XML.
<fixml version="1.0" encoding="utf-8"fi>
<Chapter6 xmlns="http://www.Sample.com/Items">
<Icecream>
<Name>Chocolate Fudge Icecream</Name>
<Type>Chocolate</Type>
<Ingredients>cream, milk, sugar, corn syrup, cellulose gum...
<Ingredients>
<Cholestrol>50mg</Cholestrol>
<TotalCarbohydrates>35g</TotalCarbohydrates>
<Price>10.5</Price>
<Protein>
<VitaminA>3g</VitaminA>
<Calcium>1g</Calcium>
<Iron>1g</Iron>
</Protein>
<TotalFat>
<SaturatedFat>9g</SaturatedFat>
<TransFat>11g</TransFat>
</TotalFat>
</Icecream>
<Icecream>
<Name>Vanilla Icecream</Name>
<Type>Vanilla</Type>
<Ingredients>vanilla extract, guar gum, cream, nonfat milk, sugar,
locust bean gum, carrageenan, annatto color...</Ingredients>
<Cholestrol>65mg</Cholestrol>
<TotalCarbohydrates>26g</TotalCarbohydrates>
<Price>9.5</Price>
<Protein>
<VitaminA>1g</VitaminA>
<Calcium>2g</Calcium>
<Iron>1g</Iron>
</Protein>
<TotalFat>
<SaturatedFat>7g</SaturatedFat>
<TransFat>9g</TransFat>
</TotalFat> </Icecream>
<Icecream>
<Name>Banana Split Chocolate Icecream</Name>
<Type>Chocolate</Type>
<Ingredients>Banana, guar gum, cream, nonfat milk, sugar, alomnds, raisins,
honey, locust bean gum, chocolate, annatto color...
</Ingredients>
<Cholestrol>58mg</Cholestrol>
<TotalCarbohydrates>24g</TotalCarbohydrates>
<Price>11</Price>
<Protein>
<VitaminA>2g</VitaminA>
<Calcium>1g</Calcium>
<Iron>1g</Iron>
</Protein>
<TotalFat>
<SaturatedFat>7g</SaturatedFat>
<TransFat>6g</TransFat>
</TotalFat>
</Icecream>
</Chapter6>
Un-typed XML
Following is a sample query for accessing data from an un-typed XML, shown pereviously:
XNamespace ns = "http://www.sample.com/Items"; return(
from icecreams in Items.Elements(ns + "Icecreams")
from item in icecreams.Elements(ns + "Icecream")
select item.Element(ns + "Price"),
item.Element(ns + "Name")
);
The query uses a namespace, ns
. This namespace is used to uniquely identify the XML elements. It is prefixed with all the elements used in the query. Each element of the XML is accessed using Element
object. The select
statement in the query above uses Element
object to access the value of Price
and Name
of each Icecream
in the XML document.
Typed XML
The following code is the XML Schema (XSD) contract for the sample XML that we have in the previous section. This schema defines a namespace for the XML, a type for the XML element and its maximum and minimum occurrences. It also describes the name and type for each element in the XML.
<fixml version="1.0" encoding="utf-8" fi>
<xs:schema id="IcecreamsSchema"
targetNamespace="http://www.Sample.com/Items"
elementFormDefault="qualified"
xmlns="http://www.Sample.com/Items"
xmlns:mstns="http://www.Sample.com/Items"
xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:element name="Chapter6">
<xs:complexType>
<xs:sequence>
<xs:element ref="Icecream"
minOccurs="0" maxOccurs="unbounded"/>
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="Icecream">
<xs:complexType>
<xs:sequence>
<xs:element name="Name" type="xs:string"/>
<xs:element name="Type" type="xs:string"/>
<xs:element name="Ingredients" type="xs:string"/>
<xs:element name="Cholestrol" type="xs:string"/>
<xs:element name="TotalCarbohydrates" type="xs:string"/>
<xs:element name="Price" type="xs:double"/>
<xs:element ref="Protein" minOccurs="0" maxOccurs="1"/>
<xs:element ref="TotalFat" minOccurs="0" maxOccurs="1"/>
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="Protein">
<xs:complexType>
<xs:sequence>
<xs:element name="VitaminA" type="xs:string"/>
<xs:element name="Calcium" type="xs:string"/>
<xs:element name="Iron" type="xs:string"/>
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="TotalFat">
<xs:complexType>
<xs:sequence>
<xs:element name="SaturatedFat" type="xs:string"/>
<xs:element name="TransFat" type="xs:string"/>
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:schema>
LINQ to XSD automatically creates classes from the XML schema used for the XML. These classes provide typed views of XML elements. Instances of LINQ to XSD types are referred to as XML Objects. Instances of LINQ to XSD classes are wrappers-around instances of the LINQ to XML class, XElement
. All LINQ to XSD classes have a common base class, XTypedElement
. This is contained in the Microsoft.Xml.Schema.Lin
library.
public class XTypedElement
{
private XElement xElement;
}
The element declaration is mapped to a subclass of XTypedElement
, shown as follows:
public class Icecream : XTypedElement
{
}
These classes contain a default constructor, and properties for the elements and attributes. It also provides methods like Load
, Save
, Clone
, etc. When XML is typed, the XML tree is readily loaded into the instance of the generated classes. Here, the type casting of the elements is not required for querying the XML elements.
Creating Typed XML using Visual Studio
Visual Studio gives IDE support for the LINQ to XSD feature. It automates the mapping of schemas to classes. The following example is based on LINQ to XSD, Preview Alpha 2.0 with Visual Studio Beta 1.
Using the New Project option, create a LINQ to XSD Console Application.
Using the Add New Item dialog, select the option to create an XML file under the project. If you have an XML file already, add it to the project using Add Existing Item option or copy-paste the contents of the available XML into the new XML file that is added to the project.
Similarly add the XML schema file to the project. After adding the schema file, copy the schema content given in the typed XML section into the file.
Now we have an XML file and an XML schema file, but we need a tool to generate an object model for the schema. Open the Properties window of the XML schema file and select LinqToXsdSchema under Build Action. This is to instruct the tool to consider the schema file in its build process.
The object model for the XML schema will get generated only after the build process. This build process also enables the IntelliSense for the generated classes, and also displays the information in the Object Browser window. To view the object browser, select the menu option View | Object Browser from the Visual Studio IDE. This will display the hierarchy of all the objects present in the current project.
Now we can code with objects using IntelliSense, as shown in the following screenshot:
We can also get a list of objects with the use of IntelliSense, as shown in the following screenshot:
Object Construction
LINQ to XML provides a powerful feature called functional construction, which is the ability to create an XML tree in a single statement. All attributes and elements are listed as arguments of XElement
constructor. XElement
constructor takes various types of arguments for content. The argument can be an XElement
, XAttribute
, or an array of objects, so that we can pass any number of objects to the XElement
. The functional construction feature is mainly used for navigating and modifying the elements and attributes of an XML tree. It actually transforms one form of data into another, instead of manipulating it.
The following code shows how to build an un-typed XML tree using functional construction. Here, we use XElement
to build a new Icecream
element and add it to the existing Icecream
element. For adding each element, we have to use XElement
with the element name and value as parameters.
Icecream.Add
(
new XElement("Icecream",
new XElement("Name", "Rum Raisin Ice Cream"),
new XElement("Ingredients", "Rum, guar gum, nonfat milk, cream,
alomnds,
sugar, raisins, honey, chocolate, annatto color..."),
new XElement("Cholesterol", "49mg"),
new XElement("TotalCarbohydrates", "28g"),
new XElement("Protein",
new XElement("VitaminA", "2g"),
new XElement("Calcium", "1g"),
new XElement("Iron", "4g")),
new XElement("TotalFat", "16g",
new XElement("SaturatedFat", "5g"),
new XElement("TransFat", "3g"))
)
);
Now we will see how to add a new element to the typed XML, without using XElement
. We can directly use the objects to add the elements.
var newObj = new Icecream
{ Name = "Rum Raisin Ice Cream", Ingredients = "Rum, guar gum, nonfat milk,
cream, alomnds, sugar, raisins, honey, chocolate, annatto color...",
Cholestrol = "49mg", TotalCarbohydrates = "28g", Protein = new Protein
{VitaminA = "2g", Iron = "4g",
Calcium = "1g"}, Price = 10.25, TotalFat = new TotalFat {SaturatedFat = "5g",
TransFat = "3g"}
};
chapter6.Icecream.Add(newObj);
Load Method
This is similar to the Load
method that we saw for LINQ to XML, but the difference is that the Load
method here is the typed version of the LINQ to XML's Load
method. Below is a sample which loads the XML file.
var chapter6 = Chapter6.Load("Icecreams.xml");
Here chapter6
is a specific type which is already defined. The un-typed loading in LINQ to XML can be made typed by casting the un-typed loading with the type that is required.
In this example, you can see the casting of Chapter6
done to the XElement
, used for loading the XML document into chapterSix,
which is a variable equivalent to the typed XML, chapter6
.
Parse Method
This method is the typed version of the Parse
method used in LINQ to XML. Parsing is used to convert a string containing XML into XElement
and cast that instance to a type required. Following is an example which parses a string containing XML, and types it to Chapter6
.
var chapter6Parse = Chapter6.Parse( " <Chapter6 xmlns='http://www.
Sample.com/Items'> <Icecream> " +
" " +
" <Name>Chocolate Fudge Icecream</Name> "+
" <Type>Chocolate</Type> "+
" <Ingredients>cream, milk, sugar, corn syrup, cellulose gum...</
Ingredients> " +
" <Cholestrol>50mg</Cholestrol> " +
" <TotalCarbohydrates>35g</TotalCarbohydrates>" +
" <Price>10.5</Price> " +
" <Protein> " +
" <VitaminA>3g</VitaminA> " +
" <Calcium>1g</Calcium> " +
" <Iron>1g</Iron> " +
" </Protein> " +
" <TotalFat> " +
" <SaturatedFat>9g</SaturatedFat> " +
" <TransFat>11g</TransFat> " +
" </TotalFat> " +
" </Icecream></Chapter6> "
);
Save Method
This method is the typed version of the Save
method used by LINQ to XML. This method outputs the XML tree as a file, a TextWriter,
or an XmlWriter
.
chapter6.Save(@"c:\LINQtoXSDSave.xml");
chapter6.Save(TextWriter testTextWriter);
chapter6.Save(XmlWriter testXmlWriter);
The above code saves the XML tree in chapter6
object to a file named LINQtoXSDSave.xml
.
Clone Method
The XTypedElement
base class used for all the generated classes defines a Clone
method. The result of the clone operation is weakly typed, as the clone is applied to the underlying un-typed XML tree. So, to get a specific type, a casting must be used while cloning.
var chapter6 = Chapter6.Load("Icecreams.xml");
var chapter6Clone = (Chapter6)chapter6.Clone();
var Qry1 = from Icecream in chapter6Clone.Icecream
select new { Icecream.Price, Icecream.Name };
Console.WriteLine(" ");
Console.WriteLine("Clone Sample ");
foreach (var itm in Qry1)
Console.WriteLine("Price of {0} is : {1}", itm.Name , itm.Price);
In the above example, we are loading the XML into chapter6
variable, and then creating a clone for it. We are type casting the new clone of the type Chapter6
, and then assigning the resultant clone to chapter6Clone
. Even though we have not assigned any XML to chapter6Clone
, the query produces the same result as that of the same query applied on chapter6
XML. Internally, the XML is the same for both objects, as chapter6Clone
is just a clone of chapter6
.
Default Values
Default is the value returned for the elements in the XML, in case the value of the XML element is empty in the XML tree. The same applies to the attributes also, but in case of attributes, they may not be present in the XML tree. The default value is specified in the XML fragment.
<xs:element name="Protein">
<xs:complexType>
<xs:sequence>
<xs:element name="VitaminA" type="xs:string" default="0g"/>
<xs:element name="Calcium" type="xs:string" default="0g"/>
<xs:element name="Iron" type="xs:string" default="0g"/>
</xs:sequence>
</xs:complexType>
</xs:element>
In the above example, the elements VitaminA
, Calcium
, and Iron
are the three elements that have a default value of 0g
. So if the XML tree does not have any value specified for these elements, the resulting value for these elements would be 0g.
Customization of XML Objects
The various types of customizations used in LINQ are explained in the following subsections.
Mapping Time Customization
There is a configuration file that controls the LINQ to XSD mapping details. XML namespaces can be mapped to CLR namespaces. For example, the default mapping for http://www.Sample.com/Items would be www.Sample.com.Items. The following example maps http://www.Sample.com/Items to LinqToXsdExample. Schema.Items:
<Configuration xmlns="http://www.microsoft.com/xml/schema/linq">
<Namespaces>
<Namespace Schema="http://www.Sample.com/Items" Clr="LinqToXsdExample.Schema.Items"/>
</Namespaces>
</Configuration>
This is also used for systematic conversion of nested, anonymous complex types into complex type definitions.
A configuration file is:
- An XML file with a designated namespace.
- Used by the command line processor of LINQ to XSD.
- Used in Visual Studio for LINQ to XSD project. The build action can be specified as
LinqToXsdConfiguration
.
We can map the Schema without a target namespace to a CLR namespace.
Compile Time Customization
LINQ to XSD generates classes, and provides the object model which can be customized using .NET. It is not so easy to customize the generated code as it requires a lot of understanding. Even if we customize the generated code, the customization will be lost if the code gets regenerated. The best option is to use sub-classing or extension of partial
classes by which we can add methods to the generated class by LINQ to XSD.
Following is the object model for our chapter6 XML
where chapter6, Icecream, Protein, and TotalFat are all generated as classes.
Now we can create a partial class for the corresponding classes, and add methods to override the base class functionality.
The LINQ to XSD Visual Studio projects use the obj\Debug\LinqToXsdSource.cs
file to hold the generated classes.
Post Compile Customization
For customizing the classes at compile time, we can use partial classes. If the object models are available in compiled format, and we do not have the source for the generated classes, we can use the extension methods to add the behaviour to the compiled objects. The LINQ to XML annotation facility can be used for this.
Using LINQ to XSD at Command Line
There is a command line tool called LinqToXsd.exe
which is an XSD to class mapper. This tool provides two options to convert XSD:
- Generating a DLL from XSD.
- Generating a .cs file from XSD, which is a default.
For example, following is the command to generate a DLL from an XSD from the location where the LINQ to XSD is installed:
LinqToXsd.exe Items.xsd /lib: Items.dll
Summary
In this chapter, we have seen the different features that are going to come up with LINQ to XSD. We have also seen examples for some of the features supported by LINQ to XSD. This makes the programmer's job easier by turning the un-typed XML to typed XML. LINQ to XSD generates the classes for XML elements, and also provides an object model, which the programmer can directly access, just as he or she would do with .NET objects.