XML Schemas

 
Chapter 9 - Data Access with .NET
bySimon Robinsonet al.
Wrox Press 2002
  

XML is firmly entrenched into ADO.NET - indeed, the remoting format for passing data between objects is now XML. With the .NET runtime, it is now possible to describe a DataTable within an XML schema definition file (XSD). What's more, you can define an entire DataSet , with a number of DataTable s, a set of relationships between these tables, and include various other details to fully describe the 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 above. In this section we'll start with a simple XSD file that describes the same information as the Products sample previously shown, and then extend this to include some extra functionality. This file is Products.xsd , found in the 10_XSD_DataSet folder:

   <?xml version="1.0" encoding="utf-8" ?>     <xs:schema     id="Products"     targetNamespace="http://tempuri.org/XMLSchema1.xsd"     elementFormDefault="qualified"     xmlns="http://tempuri.org/XMLSchema1.xsd"     xmlns:mstns="http://tempuri.org/XMLSchema1.xsd"     xmlns:xsd="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" 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>   

We'll take a closer look at some of the options within this file in Chapter 11; 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 onto 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 sub-element maps to a class derived from DataColumn . The collection of all columns maps onto a class derived from DataRow .

Thankfully there is a tool within the .NET Framework that will produce all of the code for these classes given only 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 above 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 .

There are various switches that can be used with XSD to alter the output generated. Some of the more commonly used are shown in the table below.

Switch

Description

/dataset (/d)

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 >

Define the namespace that the generated code should reside within. The default is no namespace.

An abridged version of the output from XSD for the Products schema is shown below. I've removed some of the less necessary code to concentrate on the most important aspects, and done some reformatting so that it will fit within the confines of a couple of pages. 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. The example includes the entire sourcecode plus the Product.xsd file, and can be found in the 10_XSD_DataSet directory:

   //------------------------------------------------------------------------------     // <autogenerated>     //     This code was generated by a tool.     //     Runtime Version: 1.0.3512.0     //     //     Changes to this file may cause incorrect behavior and will be lost if     //     the code is regenerated.     // </autogenerated>     //------------------------------------------------------------------------------     //     // This source code was auto-generated by xsd, Version=1.0.3512.0.     //     using System;     using System.Data;     using System.Xml;     using System.Runtime.Serialization;     [Serializable()]     [System.ComponentModel.DesignerCategoryAttribute("code")]     [System.Diagnostics.DebuggerStepThrough()]     [System.ComponentModel.ToolboxItem(true)]     public class Products : 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 class ProductDataTable : DataTable, System.Collections.IEnumerable     [System.Diagnostics.DebuggerStepThrough()]     public class ProductRow : DataRow     }   

I have taken some liberties with this sourcecode, as I have split it into three sections and removed any protected and private members so that we can concentrate on the public interface. The emboldened ProductDataTable and ProductRow definitions show the positions of two nested classes, which we're going to implement next . We'll look at the code for these after a brief explanation of the DataSet derived class.

The Products() constructor calls a private method, InitClass() , which constructs an instance of the DataTable class derived class ProductDataTable , and adds the table to the Tables collection of the DataSet . 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;   

As the Product property is strongly typed, you could naturally use ProductDataTable rather than the DataTable reference I showed above.

The ProductDataTable class includes far more code:

   [System.Diagnostics.DebuggerStepThrough()]     public 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;         internal ProductDataTable() : base("Product")     {     this.InitClass();     }   

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 the DataRow class described later uses.

   [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, except unfortunately by name) AddProductRow() methods . The first takes an already constructed DataRow and returns a void. The latter 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 and returns the row to the caller. Such widely different functions shouldn't really have the same name, in my opinion.

   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 , the InitClass() member in ProductDataTable adds in columns to the DataTable . 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.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()));     }   

The last method I want to discuss, NewRowFromBuilder() , is called internally from the DataTable 's NewRow() method. Here it creates a new strongly typed row. The DataRowBuilder instance is created by the DataTable , and its members are only accessible 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 example below 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;     }     }   

Now that the sourcecode for these data access classes has been generated by XSD.EXE , we can incorporate the classes into code. The following code utilizes these classes 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)\NetSDK;" +                       "uid=QSUser;pwd=QSPassword;" +                        "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 utilizes 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#. 2nd Edition
Performance Consulting: A Practical Guide for HR and Learning Professionals
ISBN: 1576754359
EAN: 2147483647
Year: 2002
Pages: 244

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