Xml Schemas


XML is firmly entrenched in ADO.NET — indeed, the remoting format for passing data between objects is now XML. With the .NET runtime, it is possible to describe a DataTable class within an XML schema definition file (XSD). What's more, you can define an entire DataSet class, with a number of DataTable classes, a set of relationships between these tables, and include various other details to fully describethe data.

When you have defined an XSD file, there is a new tool in the runtime that will convert this schema to the corresponding data access class(es), such as the type-safe product DataTable class shown earlier. Let's start with a simple XSD file (Products.xsd) that describes the same information as the Products sample discussed earlier and then extend it to include some extra functionality:

 <?xml version="1.0" encoding="utf-8" ?> <xs:schema  targetNamespace="http://tempuri.org/XMLSchema1.xsd"   xmlns:mstns="http://tempuri.org/XMLSchema1.xsd"  xmlns:xs="http://www.w3.org/2001/XMLSchema"  xmlns:msdata="urn:schemas-microsoft-com:xml-msdata"> <xs:element name="Product"> <xs:complexType> <xs:sequence> <xs:element name="ProductID" msdata:ReadOnly="true" msdata:AutoIncrement="true" type="xs:int" /> <xs:element name="ProductName" type="xs:string" /> <xs:element name="SupplierID" type="xs:int" minOccurs="0" /> <xs:element name="CategoryID" type="xs:int" minOccurs="0" /> <xs:element name="QuantityPerUnit" type="xs:string" minOccurs="0" /> <xs:element name="UnitPrice" type="xs:decimal" minOccurs="0" /> <xs:element name="UnitsInStock" type="xs:short" minOccurs="0" /> <xs:element name="UnitsOnOrder" type="xs:short" minOccurs="0" /> <xs:element name="ReorderLevel" type="xs:short" minOccurs="0" /> <xs:element name="Discontinued" type="xs:boolean" /> </xs:sequence> </xs:complexType> </xs:element> </xs:schema> 

These options are covered in detail in Chapter 21, "Manipulating XML"; for now, this file basically defines a schema with the id attribute set to Products. A complex type called Product is defined, which contains a number of elements, one for each of the fields within the Products table.

These items map to data classes as follows. The Products schema maps to a class derived from DataSet. The Product complex type maps to a class derived from DataTable. Each subelement maps to a class derived from DataColumn. The collection of all columns maps to a class derived from DataRow.

Thankfully, there is a tool within the .NET Framework that produces the code for these classes with the help of the input XSD file. Because its sole job in life is to perform various functions on XSD files, the tool itself is called XSD.EXE.

Generating Code with XSD

Assuming you save the preceding file as Product.xsd, you would convert the file into code by issuing the following command in a command prompt:

 xsd Product.xsd /d 

This creates the file Product.cs.

Various switches can be used with XSD to alter the output generated. Some of the more commonly used are shown in the following table.

The following is an abridged version of the output from XSD for the Products schema. The output has been altered slightly to fit into a format appropriate for the book. To see the complete output, run XSD.EXE on the Products schema (or one of your own making) and take a look at the .cs file generated.

Switch

Description

/dataset(/d)

Enables you to generate classes derived from DataSet, DataTable, and DataRow.

/language:<language>

Permits you to choose which language the output file will be written in. C# is the default, but you can choose VB for a Visual Basic .NET file.

/namespace:<namespace>

Enables you to define the namespace that the generated code should reside within. The default is no namespace.

The example includes the entire source code plus the Product.xsd file. (Note that this output is part of the downloadable code file at www.wrox.xom):

 //------------------------------------------------------------------------------ // <autogenerated> //     This code was generated by a tool. //     Runtime Version:2.0.40426.16 // //     Changes to this file may cause incorrect behavior and will be lost if //     the code is regenerated. // </autogenerated> //------------------------------------------------------------------------------ using System; //  // This source code was auto-generated by xsd, Version=2.0.40426.16. //  [Serializable()] [System.ComponentModel.DesignerCategoryAttribute("code")] [System.Diagnostics.DebuggerStepThrough()] [System.ComponentModel.ToolboxItem(true)] [System.Xml.Serialization.XmlSchemaProviderAttribute("GetTypedDataSetSchema")] [System.Xml.Serialization.XmlRootAttribute("Products")] public partial class Products : System.Data.DataSet { { private ProductDataTable tableProduct; public Products() public ProductDataTable Product  public override DataSet Clone() public delegate void ProductRowChangeEventHandler ( object sender, ProductRowChangeEvent e); [System.Diagnostics.DebuggerStepThrough()] public partial class ProductDataTable : DataTable, IEnumerable [System.Diagnostics.DebuggerStepThrough()] public class ProductRow : DataRow } 

All private and protected members have been removed to concentrate on the public interface. The emboldened ProductDataTable and ProductRow definitions show the positions of two nested classes, which will be implemented next. You review the code for these classes after a brief explanation of the DataSet-derived class.

The Products() constructor calls a private method, InitClass(), which constructs an instance of the DataTable-derived class ProductDataTable, and adds the table to the Tables collection of the DataSet class. The Products data table can be accessed by the following code:

 DataSet ds = new Products();  DataTable products = ds.Tables["Products"]; 

Or, more simply by using the property Product, available on the derived DataSet object:

 DataTable products = ds.Product; 

Because the Product property is strongly typed, you could naturally use ProductDataTable rather than the DataTable reference shown in the previous code.

The ProductDataTable class includes far more code (note this is an abridged version of the code):

 [System.Serializable()]  [System.Diagnostics.DebuggerStepThrough()] [System.Xml.Serialization.XmlSchemaProviderAttribute("GetTypedTableSchema")] public partial class ProductDataTable : DataTable, System.Collections.IEnumerable  { private DataColumn columnProductID; private DataColumn columnProductName; private DataColumn columnSupplierID; private DataColumn columnCategoryID; private DataColumn columnQuantityPerUnit; private DataColumn columnUnitPrice; private DataColumn columnUnitsInStock; private DataColumn columnUnitsOnOrder; private DataColumn columnReorderLevel; private DataColumn columnDiscontinued; public ProductDataTable()   { this.TableName = "Product"; this.BeginInit(); this.InitClass(); this.EndInit();   } 

The ProductDataTable class, derived from DataTable and implementing the IEnumerable interface, defines a private DataColumn instance for each of the columns within the table. These are initialized again from the constructor by calling the private InitClass() member. Each column is given an internal accessor, which is used by the DataRow class (which is described shortly):

 [System.ComponentModel.Browsable(false)] public int Count { get { return this.Rows.Count; } } internal DataColumn ProductIDColumn { get { return this.columnProductID; } } // Other row accessors removed for clarity -- there is one for each of the columns 

Adding rows to the table is taken care of by the two overloaded (and significantly different) AddProduct Row() methods. The first takes an already constructed DataRow and returns a void. The second takes a set of values, one for each of the columns in the DataTable, constructs a new row, sets the values within this new row, adds the row to the DataTable object, and returns the row to the caller. Such widely different functions shouldn't really have the same name!

 public void AddProductRow(ProductRow row) { this.Rows.Add(row); } public ProductRow AddProductRow ( string ProductName , int SupplierID , int CategoryID , string QuantityPerUnit , System.Decimal UnitPrice , short UnitsInStock , short UnitsOnOrder , short ReorderLevel , bool Discontinued ) { ProductRow rowProductRow = ((ProductRow)(this.NewRow())); rowProductRow.ItemArray = new object[]  { null, ProductName, SupplierID, CategoryID, QuantityPerUnit, UnitPrice, UnitsInStock, UnitsOnOrder, ReorderLevel, Discontinued }; this.Rows.Add(rowProductRow); return rowProductRow; } 

Just like the InitClass() member in the DataSet-derived class, which added the table into the DataSet class, the InitClass() member in ProductDataTable adds columns to the DataTable class. Each column's properties are set as appropriate, and the column is then appended to the columns collection:

 private void InitClass() { this.columnProductID = new DataColumn ( "ProductID",  typeof(int), null, System.Data.MappingType.Element); this.columnProductID.ExtendedProperties.Add ("Generator_ChangedEventName", "ProductIDChanged"); this.columnProductID.ExtendedProperties.Add ("Generator_ChangingEventName", "ProductIDChanging"); this.columnProductID.ExtendedProperties.Add ("Generator_ColumnPropNameInRow", "ProductID"); this.columnProductID.ExtendedProperties.Add ("Generator_ColumnPropNameInTable", "ProductIDColumn"); this.columnProductID.ExtendedProperties.Add ("Generator_ColumnVarNameInTable", "columnProductID"); this.columnProductID.ExtendedProperties.Add ("Generator_DelegateName", "ProductIDChangeEventHandler"); this.columnProductID.ExtendedProperties.Add ("Generator_EventArgName", "ProductIDChangeEventArg"); this.Columns.Add(this.columnProductID); // Other columns removed for clarity this.columnProductID.AutoIncrement = true; this.columnProductID.AllowDBNull = false; this.columnProductID.ReadOnly = true; this.columnProductName.AllowDBNull = false; this.columnDiscontinued.AllowDBNull = false; } public ProductRow NewProductRow() { return ((ProductRow)(this.NewRow())); } 

NewRowFromBuilder() is called internally from the DataTable class's NewRow() method. Here, it creates a new strongly typed row. The DataRowBuilder instance is created by the DataTable class, and its members are accessible only within the System.Data assembly:

 protected override DataRow NewRowFromBuilder(DataRowBuilder builder) { return new ProductRow(builder); } 

The last class to discuss is the ProductRow class, derived from DataRow. This class is used to provide type-safe access to all fields in the data table. It wraps the storage for a particular row, and provides members to read (and write) each of the fields in the table.

In addition, for each nullable field, there are functions to set the field to null, and check if the field is null. The following example shows the functions for the SupplierID column:

 [System.Diagnostics.DebuggerStepThrough()] public class ProductRow : DataRow { private ProductDataTable tableProduct; internal ProductRow(DataRowBuilder rb) : base(rb) { this.tableProduct = ((ProductDataTable)(this.Table)); } public int ProductID { get { return ((int)(this[this.tableProduct.ProductIDColumn])); } set { this[this.tableProduct.ProductIDColumn] = value; } } // Other column accessors/mutators removed for clarity public bool IsSupplierIDNull() { return this.IsNull(this.tableProduct.SupplierIDColumn); } public void SetSupplierIDNull() { this[this.tableProduct.SupplierIDColumn] = System.Convert.DBNull; } } 

The following code utilizes the classes ouptut from the XSD tool to retrieve data from the Products table and display that data to the console:

using System; using System.Data; using System.Data.SqlClient; public class XSD_DataSet {    public static void Main()    {       string source = "server=(local);" +                        " integrated security=SSPI;" +                        "database=northwind";       string select = "SELECT * FROM Products";       SqlConnection conn = new SqlConnection(source);       SqlDataAdapter da = new SqlDataAdapter(select , conn); Products ds = new Products();       da.Fill(ds , "Product"); foreach(Products.ProductRow row in ds.Product )       Console.WriteLine("'{0}' from {1}" , row.ProductID , row.ProductName);    } }

The main areas of interest are highlighted. The output of the XSD file contains a class derived from DataSet, Products, which is created and then filled by the use of the data adapter. The foreach statement uses the strongly typed ProductRow and also the Product property, which returns the Product data table.

To compile this example, issue the following commands:

 xsd product.xsd /d 

and

 csc /recurse:*.cs 

The first generates the Products.cs file from the Products.XSD schema, and then the csc command utilizes the /recurse:*.cs parameter to go through all files with the extension .cs and add these to the resulting assembly.




Professional C# 2005
Pro Visual C++ 2005 for C# Developers
ISBN: 1590596080
EAN: 2147483647
Year: 2005
Pages: 351
Authors: Dean C. Wills

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net