9.

The XSD Program

 

This chapter acquaints you with the XML Schema Definition tool (XSD), which generates either the XML schema or the classes in diverse languages, such as C#, Visual Basic and JavaScript. The default language is C#. The input to XSD can be either one of the following files: XDR files, xml files or XSD files. Furthermore, the classes dwelling in a DLL file or an exe file can also be supplied to the XSD tool.

 

We cast off with the simplest form, wherein an xml file has to be converted into an xsd file.

 

b.xml

<?xml version="1.0" ?>

 

The XSD command is executed as follows:

 

c:\xmlprg>xsd b.xml

Microsoft (R) Xml Schemas/DataTypes support utility

[Microsoft (R) .NET Framework, Version 1.0.3328.4]

Copyright (C) Microsoft Corporation 1998-2001. All rights reserved.

 

Error: There was an error processing 'b.xml'.

  - The root element is missing.

 

If you would like more help, please type "xsd /?".

 

The program generates the above error. This error can be attributed to the fact that the file is devoid of the mandatory root or starting element, which every xml file must possess. The xml file is now modified to include a single root element named zzz.

 

b.xml

<?xml version="1.0" ?>

<zzz />

 

b.xsd

<?xml version="1.0" encoding="utf-8"?>

<xs:schema id="zzz" xmlns="" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">

<xs:element name="zzz" msdata:IsDataSet="true">

<xs:complexType>

<xs:choice maxOccurs="unbounded" />

</xs:complexType>

</xs:element>

</xs:schema>

 

The above file is generated by the XSD program.

In the xsd file, the schema element has an id, which is similar to the root tag. The targetNamespace is left blank. The familiar old xs namespace prefix points to the default URI. The namespace prefix needs to be initialized appropriately, since the attributes are being used from the msdata namespace.

 

This is followed by the root element zzz with the attribute IsDataSet set to true, since it is assumed to be a data set. Since there are no child items within the zzz root, the choice is left empty within the anonymous complex type.

 

b.xml

<?xml version="1.0" ?>

<zzz>

<aaa> hi </aaa>

<bbb> 10 </bbb>

</zzz>

 

b.xsd

<?xml version="1.0" encoding="utf-8"?>

<xs:schema id="NewDataSet" xmlns="" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">

<xs:element name="zzz">

<xs:complexType>

<xs:sequence>

<xs:element name="aaa" type="xs:string" minOccurs="0" />

<xs:element name="bbb" type="xs:string" minOccurs="0" />

</xs:sequence>

</xs:complexType>

</xs:element>

<xs:element name="NewDataSet" msdata:IsDataSet="true">

<xs:complexType>

<xs:choice maxOccurs="unbounded">

<xs:element ref="zzz" />

</xs:choice>

</xs:complexType>

</xs:element>

</xs:schema>

 

The xml file has two child elements named aaa and bbb within the root element zzz, containing 'hi' and 10, respectively. In the xsd file, the schema id changes to the NewDataSet. A new element zzz gets created, which was not present earlier.

 

The sequence element now contains the two child elements named aaa and bbb. The IsDataSet element, which is contained in the element NewDataSet, is assigned the value of true and in the choice, it now refers to the element zzz.

 

The above two examples amply substantiate the fact that an xsd file can be created from an existing Xml file. However, what is more imperative is the creation of a run time class from an xsd file.

 

b.xsd

<?xml version="1.0" encoding="utf-8"?>

<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">

</xs:schema>

 

Create the smallest possible xsd file as depicted above, embodying  merely the schema element and the xs namespace prefix. Now, execute the XSD program as follows:

 

c:\xmlprg>xsd /d b.xsd

Microsoft (R) Xml Schemas/DataTypes support utility

[Microsoft (R) .NET Framework, Version 1.0.3328.4]

Copyright (C) Microsoft Corporation 1998-2001. All rights reserved.

 

Writing file 'c:\xmlprg\b.cs'.

 

The /d option directs the XSD program to generate a Dataset class. The filename is b.cs, having contents as depicted below:

 

b.cs

//------------------------------------------------------------------------------

// <autogenerated>

//     This code was generated by a tool.

//     Runtime Version: 1.0.3328.4

//

//     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.3328.4.

//

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 NewDataSet : DataSet {

    public NewDataSet() {

        this.InitClass();

        System.ComponentModel.CollectionChangeEventHandler schemaChangedHandler = new System.ComponentModel.CollectionChangeEventHandler(this.SchemaChanged);

        this.Tables.CollectionChanged += schemaChangedHandler;

        this.Relations.CollectionChanged += schemaChangedHandler;

   }

   protected NewDataSet(SerializationInfo info, StreamingContext context) {

    string strSchema = ((string)(info.GetValue("XmlSchema", typeof(string))));

        if ((strSchema != null)) {

            DataSet ds = new DataSet();

            ds.ReadXmlSchema(new XmlTextReader(new System.IO.StringReader(strSchema)));

            this.DataSetName = ds.DataSetName;

            this.Prefix = ds.Prefix;

            this.Namespace = ds.Namespace;

            this.Locale = ds.Locale;

            this.CaseSensitive = ds.CaseSensitive;

            this.EnforceConstraints = ds.EnforceConstraints;

            this.Merge(ds, false, System.Data.MissingSchemaAction.Add);

            this.InitVars();

        }

        else {

            this.InitClass();

        }

        this.GetSerializationData(info, context);

        System.ComponentModel.CollectionChangeEventHandler schemaChangedHandler = new System.ComponentModel.CollectionChangeEventHandler(this.SchemaChanged);

        this.Tables.CollectionChanged += schemaChangedHandler;

        this.Relations.CollectionChanged += schemaChangedHandler;

    }

    public override DataSet Clone() {

        NewDataSet cln = ((NewDataSet)(base.Clone()));

        cln.InitVars();

        return cln;

    }

    protected override bool ShouldSerializeTables() {

        return false;

    }

    protected override bool ShouldSerializeRelations() {

        return false;

    }

    protected override void ReadXmlSerializable(XmlReader reader)                {

        this.Reset();

        DataSet ds = new DataSet();

        ds.ReadXml(reader);

        this.DataSetName = ds.DataSetName;

        this.Prefix = ds.Prefix;

        this.Namespace = ds.Namespace;

        this.Locale = ds.Locale;

        this.CaseSensitive = ds.CaseSensitive;

        this.EnforceConstraints = ds.EnforceConstraints;

        this.Merge(ds, false, System.Data.MissingSchemaAction.Add);

        this.InitVars();

    }

    protected override System.Xml.Schema.XmlSchema GetSchemaSerializable() {

        System.IO.MemoryStream stream = new System.IO.MemoryStream();

        this.WriteXmlSchema(new XmlTextWriter(stream, null));

        stream.Position = 0;

        return System.Xml.Schema.XmlSchema.Read(new XmlTextReader(stream), null);

    }

    internal void InitVars() {

    }

    private void InitClass() {

        this.DataSetName = "NewDataSet";

        this.Prefix = "";

        this.Namespace = "";

        this.Locale = new System.Globalization.CultureInfo("en-US");

        this.CaseSensitive = false;

        this.EnforceConstraints = true;

    }

    private void SchemaChanged(object sender, System.ComponentModel.CollectionChangeEventArgs e) {

        if ((e.Action == System.ComponentModel.CollectionChangeAction.Remove)) {

            this.InitVars();

        }

    }

}

 

The file b.cs is a C# program, which commences with the 'using' statements for the namespaces. If you have read our books on C#, you would surely know why these 'using' statements can be safely overlooked. A class named NewDataSet is created, and this name is chosen as the default name, since no specific name has been assigned for the DataSet class.

 

This class is also derived from the DataSet class because of the /d option that has been used with the XSD program.

 

There are two attributes that qualify the NewDataSet class. The first attribute is Serializable, which is placed on a class that can be serialized, and thus, it can never be inherited. Classes that contain pointers should not possess the Serializable attribute.

 

The second attribute is DesignerCategoryAttribute, which is associated with the class that is used to implement the design time services. In our case, the string parameter is the type that provides the design time services.

 

Both the constructors in the class NewDataSet call a function called InitClass. The 'this' keyword is redundant. The program generated code generally contains substantial amount of superfluous material, which can be safely done away with.

 

 

In the function InitClass, the name of the DataSetName property is set to NewDataSet. This property stores the name of the current DataSet, which is the actual name of the DataSet and not the name of the class derived from the DataSet class. The Namespace property is set to 'null'. This property is utilized only while differentiating between attributes and elements from the XML data into a DataSet.    

 

The constructor that accepts two parameters calls a function named GetSerializationData. The SerializationInfo parameter contains the data essential to serialize or deserialize an object. The StreamingContext parameter stipulates the source and destination of a specified serialized stream. The above parameters are used while implementing an XmlSerializable interface, which is the task of the function GetSerializationData.

 

We shall be unraveling the remaining functions created by the XSD program in a short while, but first let us expound the xsd file in greater depth.

 

b.xsd

<xsd:schema id="zzz1" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">

<xsd:element name="Customers">

<xsd:complexType>

<xsd:sequence>

<xsd:element name="CustomerID" minOccurs="0" type="xsd:string"/>

<xsd:element name="CompanyName" minOccurs="0" type="xsd:string"/>

</xsd:sequence>

</xsd:complexType>

</xsd:element>

<xsd:element name="zzz" msdata:IsDataSet="true">

<xsd:complexType>

<xsd:choice maxOccurs="unbounded">

<xsd:element ref="Customers"/>

</xsd:choice>

</xsd:complexType>

</xsd:element>

</xsd:schema>

The xsd file has the schema tag with the id attribute, that has a value of zzz1. Then ensues the element named zzz that represents a DataSet, since the attribute IsDataSet is set to true. The choice element has only one element that refers to Customers. The Customers element also has two fields named CustomerID and CompanyName.

 

We want you to visualize the xsd file as a DataSet zzz with a table Customers containing two fields named CustomerID and CompanyName.

 

>xsd.exe /d /l:C# b.xsd

 

The XSD program utility is employed to create a file named b.cs with the /d option, thereby ensuring that the class is derived from a data set class. The other option of /l that exists for the language, is optional since the default language used is C#.

 

>csc.exe /t:library b.cs

The compiler is then called to create a dll file named b.dll from the file named b.cs.

 

a.cs

using System;

using System.Data.SqlClient;

public class aaa {

public static void Main() {

zzz b = new zzz();

SqlConnection s = new SqlConnection("server=(local)\\NetSDK;database=northwind;

Trusted_Connection=yes");

SqlDataAdapter a = new SqlDataAdapter("SELECT * FROM Customers",s);

a.Fill(b, "Customers");

foreach(zzz.CustomersRow c in b.Customers)

Console.WriteLine(c.CustomerID + " " + c.CompanyName);

}

}

 

>csc a.cs /r:b.dll

The C# program above employs the code generated by the XSD program to retrieve data from a database. Firstly, the object 'b', which looks like class zzz, is created. Although the class zzz is not present anywhere in the file a.cs, no error emanates since it is present in the dll file named 'b'.

 

The XSD program creates the file b.cs, which is then used to create the file b.dll. Thus, the name of the element in the xsd file with the IsDataSet attribute set to true, determines the name of the class.

 

The SqlDataAdapter class is used to specify the Select statement that will fetch all the records from the Customers table. The second parameter is the 'connection' string that specifies the Data Source, which indicates that the server is located on the same machine.

 

The user id (uid) is 'sa' and the password is blank. The initial database or catalog is NorthWind, which contains the Customers table. The SqlDataAdapter class has a function called Fill that requires two parameters to do the mapping, viz. the dataset that is to be filled with data and the source table Customers.

 

Towards the culmination of this function call, the data set 'b' contains all the data from the SQL select statement.

 

The dataset object 'b' embodies a property called Customers, which returns an object that looks like the class CustomersRow from the zzz class. The 'foreach' statement is used to fetch the records. Thus, if there are 100 rows of data, the loop repeats a 100 times. In each iteration, 'c' represents a fresh record. The CustomersRow class has two properties, viz.  CustomerID and CompanyName, whose values are displayed.

 

When the program 'a' is executed, the output that is generated provides a list of all the customer IDs, followed by the name of the company. Both of these are valid fields in the Customers table.

 

A listing and explanation of the file b.cs now ensues.

 

 

 

b.cs

//------------------------------------------------------------------------------

// <autogenerated>

//     This code was generated by a tool.

//     Runtime Version: 1.0.3328.4

//

//     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.3328.4.

//

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 zzz : DataSet {

 

private CustomersDataTable tableCustomers;

public zzz() {

  this.InitClass();

 System.ComponentModel.CollectionChangeEventHandler    schemaChangedHandler = new System.ComponentModel.CollectionChangeEventHandler(this.SchemaChanged);

  this.Tables.CollectionChanged += schemaChangedHandler;

  this.Relations.CollectionChanged += schemaChangedHandler;

}

protected zzz(SerializationInfo info, StreamingContext context) {

  string strSchema = ((string)(info.GetValue("XmlSchema", typeof(string))));

  if ((strSchema != null)) {

  DataSet ds = new DataSet();

 ds.ReadXmlSchema(new XmlTextReader(new System.IO.StringReader(strSchema)));

if ((ds.Tables["Customers"] != null)) {

   this.Tables.Add(new CustomersDataTable(ds.Tables["Customers"]));

}

this.DataSetName = ds.DataSetName;

this.Prefix = ds.Prefix;

this.Namespace = ds.Namespace;

this.Locale = ds.Locale;

this.CaseSensitive = ds.CaseSensitive;

this.EnforceConstraints = ds.EnforceConstraints;

this.Merge(ds, false, System.Data.MissingSchemaAction.Add);

this.InitVars();

}

else {

this.InitClass();

}

this.GetSerializationData(info, context);

System.ComponentModel.CollectionChangeEventHandler schemaChangedHandler = new System.ComponentModel.CollectionChangeEventHandler(this.SchemaChanged);

this.Tables.CollectionChanged += schemaChangedHandler;

this.Relations.CollectionChanged += schemaChangedHandler;

}

[System.ComponentModel.Browsable(false)]

[System.ComponentModel.DesignerSerializationVisibilityAttribute

(System.ComponentModel.DesignerSerializationVisibility.Content)]

public CustomersDataTable Customers {

get {

      return this.tableCustomers;

}

}

public override DataSet Clone() {

zzz cln = ((zzz)(base.Clone()));

cln.InitVars();

return cln;

}

protected override bool ShouldSerializeTables() {

return false;

}

protected override bool ShouldSerializeRelations() {

return false;

}

protected override void ReadXmlSerializable(XmlReader reader) {

this.Reset();

DataSet ds = new DataSet();

ds.ReadXml(reader);

if ((ds.Tables["Customers"] != null)) {

this.Tables.Add(new CustomersDataTable(ds.Tables["Customers"]));

}

this.DataSetName = ds.DataSetName;

this.Prefix = ds.Prefix;

this.Namespace = ds.Namespace;

this.Locale = ds.Locale;

this.CaseSensitive = ds.CaseSensitive;

this.EnforceConstraints = ds.EnforceConstraints;

this.Merge(ds, false, System.Data.MissingSchemaAction.Add);

this.InitVars();

}

protected override System.Xml.Schema.XmlSchema GetSchemaSerializable() {

System.IO.MemoryStream stream = new System.IO.MemoryStream();

this.WriteXmlSchema(new XmlTextWriter(stream, null));

stream.Position = 0;

return System.Xml.Schema.XmlSchema.Read(new XmlTextReader(stream), null);

}

internal void InitVars() {

this.tableCustomers = ((CustomersDataTable)(this.Tables["Customers"]));

if ((this.tableCustomers != null)) {

this.tableCustomers.InitVars();

}

}

private void InitClass() {

this.DataSetName = "zzz";

this.Prefix = "";

this.Namespace = "";

this.Locale = new System.Globalization.CultureInfo("en-US");

this.CaseSensitive = false;

this.EnforceConstraints = true;

this.tableCustomers = new CustomersDataTable();

this.Tables.Add(this.tableCustomers);

}

private bool ShouldSerializeCustomers() {

return false;

}

private void SchemaChanged(object sender, System.ComponentModel.CollectionChangeEventArgs e) {

if ((e.Action == System.ComponentModel.CollectionChangeAction.Remove)) {

this.InitVars();

}

}

public delegate void CustomersRowChangeEventHandler(object sender, CustomersRowChangeEvent e);

[System.Diagnostics.DebuggerStepThrough()]

public class CustomersDataTable : DataTable, System.Collections.IEnumerable {

private DataColumn columnCustomerID;

private DataColumn columnCompanyName;

internal CustomersDataTable() :

base("Customers") {

this.InitClass();

}

internal CustomersDataTable(DataTable table) :

base(table.TableName) {

if ((table.CaseSensitive != table.DataSet.CaseSensitive)) {

this.CaseSensitive = table.CaseSensitive;

}

if ((table.Locale.ToString() != table.DataSet.Locale.ToString())) {

this.Locale = table.Locale;

}

if ((table.Namespace != table.DataSet.Namespace)) {

this.Namespace = table.Namespace;

}

this.Prefix = table.Prefix;

this.MinimumCapacity = table.MinimumCapacity;

this.DisplayExpression = table.DisplayExpression;

}

[System.ComponentModel.Browsable(false)]

public int Count {

get {

return this.Rows.Count;

}

}

internal DataColumn CustomerIDColumn {

get {

return this.columnCustomerID;

}

}

internal DataColumn CompanyNameColumn {

get {

return this.columnCompanyName;

}

}

public CustomersRow this[int index] {

get {

return ((CustomersRow)(this.Rows[index]));

}

}

public event CustomersRowChangeEventHandler CustomersRowChanged;

public event CustomersRowChangeEventHandler CustomersRowChanging;

public event CustomersRowChangeEventHandler CustomersRowDeleted;

public event CustomersRowChangeEventHandler CustomersRowDeleting;

public void AddCustomersRow(CustomersRow row) {

this.Rows.Add(row);

}

public CustomersRow AddCustomersRow(string CustomerID, string CompanyName) {

CustomersRow rowCustomersRow = ((CustomersRow)(this.NewRow()));

rowCustomersRow.ItemArray = new object[] {

CustomerID,CompanyName};

this.Rows.Add(rowCustomersRow);

return rowCustomersRow;

}

public System.Collections.IEnumerator GetEnumerator() {

return this.Rows.GetEnumerator();

}

public override DataTable Clone() {

CustomersDataTable cln = ((CustomersDataTable)(base.Clone()));

cln.InitVars();

return cln;

}

internal void InitVars() {

this.columnCustomerID = this.Columns["CustomerID"];

this.columnCompanyName = this.Columns["CompanyName"];

}

private void InitClass() {

this.columnCustomerID = new DataColumn("CustomerID", typeof(string), null, System.Data.MappingType.Element);

this.Columns.Add(this.columnCustomerID);

this.columnCompanyName = new DataColumn("CompanyName", typeof(string), null, System.Data.MappingType.Element);

this.Columns.Add(this.columnCompanyName);

}

public CustomersRow NewCustomersRow() {

return ((CustomersRow)(this.NewRow()));

}

protected override DataRow NewRowFromBuilder(DataRowBuilder builder) {

return new CustomersRow(builder);

}

protected override System.Type GetRowType() {

return typeof(CustomersRow);

}

protected override void OnRowChanged(DataRowChangeEventArgs e) {

base.OnRowChanged(e);

if ((this.CustomersRowChanged != null)) {

this.CustomersRowChanged(this, new CustomersRowChangeEvent(((CustomersRow)(e.Row)), e.Action));

}

}

protected override void OnRowChanging(DataRowChangeEventArgs e) {

base.OnRowChanging(e);

if ((this.CustomersRowChanging != null)) {

this.CustomersRowChanging(this, new CustomersRowChangeEvent(((CustomersRow)(e.Row)), e.Action));

}

}

protected override void OnRowDeleted(DataRowChangeEventArgs e) {

base.OnRowDeleted(e);

if ((this.CustomersRowDeleted != null)) {

this.CustomersRowDeleted(this, new CustomersRowChangeEvent(((CustomersRow)(e.Row)), e.Action));

}

}

protected override void OnRowDeleting(DataRowChangeEventArgs e) {

base.OnRowDeleting(e);

if ((this.CustomersRowDeleting != null)) {

this.CustomersRowDeleting(this, new CustomersRowChangeEvent(((CustomersRow)(e.Row)), e.Action));

}

}

public void RemoveCustomersRow(CustomersRow row) {

this.Rows.Remove(row);

}

}

[System.Diagnostics.DebuggerStepThrough()]

public class CustomersRow : DataRow {

private CustomersDataTable tableCustomers;

internal CustomersRow(DataRowBuilder rb) :

base(rb) {

this.tableCustomers = ((CustomersDataTable)(this.Table));

}

public string CustomerID {

get {

try {

return ((string)(this[this.tableCustomers.CustomerIDColumn]));

}

catch (InvalidCastException e) {

throw new StrongTypingException("Cannot get value because it is DBNull.", e);

}

}

set {

this[this.tableCustomers.CustomerIDColumn] = value;

}

}

public string CompanyName {

get {

try {

return ((string)(this[this.tableCustomers.CompanyNameColumn]));

}

catch (InvalidCastException e) {

throw new StrongTypingException("Cannot get value because it is DBNull.", e);

}

}

set {

this[this.tableCustomers.CompanyNameColumn] = value;

}

}

public bool IsCustomerIDNull() {

return this.IsNull(this.tableCustomers.CustomerIDColumn);

}

public void SetCustomerIDNull() {

this[this.tableCustomers.CustomerIDColumn] = System.Convert.DBNull;

}

public bool IsCompanyNameNull() {

return this.IsNull(this.tableCustomers.CompanyNameColumn);

}

public void SetCompanyNameNull() {

this[this.tableCustomers.CompanyNameColumn] = System.Convert.DBNull;

}

}

[System.Diagnostics.DebuggerStepThrough()]

public class CustomersRowChangeEvent : EventArgs {

private CustomersRow eventRow;

private DataRowAction eventAction;

public CustomersRowChangeEvent(CustomersRow row, DataRowAction action) {

this.eventRow = row;

this.eventAction = action;

}

public CustomersRow Row {

get {

return this.eventRow;

}

}

public DataRowAction Action {

get {

return this.eventAction;

}

}

}

}

 

The file begins with the class zzz. Thus, the name of the element containing the attribute of IsDataSet, evolves not only into the name of the dataset but also into the name of the class. This class is referred to in the C# program named a.cs. The constructors do not initiate anything innovative; and therefore, they are not discussed here.

 

In the InitClass function, the tableCustomers instance variable is initialized to a new CustomersDataTable object. The Tables property of type DataTableCollection maintains track of all the tables in the DataSet. As usual, the Add function is used to add the Customers table to the existing collection of tables in the DataSet.

 

The XSD program creates the instance variable tableCustomers as an instance of a class CustomersDataTable. What is noteworthy in the xsd file is that if the element name is changed from Customer to Customer1, all occurrences of the word 'Customer' in this file get modified to 'Customer1'. Thus, tableCustomers becomes tableCustomers1 and the class CustomersDataTable transforms into Customers1DataTable.

 

 

In the 'foreach' statement, the property of Customers in the class zzz is cited in order to return the object tableCustomers, since it represents a single record. This property is a 'read only' property, since it comprises of only the 'get' accessor.

 

There are two attributes flanking this property. The first one is the Browsable attribute, which does not get displayed in the properties window, because it has a value of false. Normally, the 'read only' property is seldom showcased in a product like Visual Studio.Net.

 

The second property named DesignerSerializationVisibilityAttribute merely identifies the manner in which the property should be saved by a designer, such as Visual Studio.Net.

 

In the normal course, products that boast of visual effects, save the values of all read/write properties by assigning the value directly to the property. This is exactly what the default value of Visible does. If the value is concealed, the designer does not serialize this property

 

The last value of content allows the serializing of the contents of the property and not of the property itself. This is ideal in a situation wherein the items in a collection need to be accessed.

 

The next class that gets created is the CustomersDataTable class. This class is derived from DataTable and it represents the actual data. This is because a dataset does not carry data; it only contains data tables, which in turn carry the data.

 

The IEnumerable interface that the class derives from, ushers in the property of a collection class. The two elements in the xsd file evolve into DataColumn objects in this class. The element CustomerID becomes columnCustomerID, i.e. the word 'column' gets added to the file or element name. The same holds true for the CompanyName too.

 

The constructor of this class first calls the base Constructor of the DataTable class, followed by a function named InitClass.

 

The function InitClass initializes the two DataColumn objects columnCustomerID and columnCompanyID. The following four parameters are passed to the constructor:

 

·    The first is the ColumnName, which is the name of the column CustomerID. 

·    The second is the data type that is lifted from the xsd file, string as a Type.

·    The third parameter is an expression that can be used to create this column, which presently is assigned a value of null, since this value currently holds no significance.

·    The fourth and final parameter is the MappingType parameter. This parameter determines how a DataColumn should be mapped on conversion to an Xml file.

 

There are four possible values that can be assigned to Attributes, to convert the value of this column into an attribute.

 

Thus, if the CustomerID is assigned the name 'Vijay' in the XML, it will get converted into the attribute CustomerID="vijay". However, if it is assigned the value of Element instead, then it will get converted into <CustomerID> vijay </CustomerID> in the Xml file. The third option is to assign the value of Hidden, in which case, it is mapped to an internal structure. The fourth possible option is the value of SimpleContent, which maps to an XmlText node.

 

As witnessed in the case of a Tables collection, these two columns are appended to the DataColumnCollection using the Add function.

 

Next, the class called CustomersRow gets created. This class is derived from DataRow. The DataRow class represents a row of data in a DataTable. This class is used to pick up values from a Data Table. Since there are two fields in the xsd file, the file contains two properties with identical names.

 

The first property is CustomerID, which is a read/write property. In the 'get' accessor, the value of the CustomerIDColumn property that is present in the tableCustomers object, is duly returned.  The CustomerIDColumn property is a 'read only' property. It simply returns the value of the DataColumn instance variable columnCustomerID.

 

The CustomerID value is used to refer into the indexer of the CustomersRow class and to fetch the actual value. The indexer of the parent DataRow class is brought into play, since there are no indexers in the class. Thereafter, the tableCustomers object, which is present in the class CustomersRow, is initialized by the constructor to the value stored in the Table property.

 

In the present case, this property represents the Table Customers. If the value of the columns happens to be null, an exception will get thrown at runtime.

 

Now, let us examine the sequence of functions and try to comprehend the ones that have not been explicated so far. In the file b.cs, the WriteLine function is placed in every single function, and thereafter, the file is compiled into a dll. In this manner, every function that gets called is verified. Moreover, the sequence and the number of times that it can get called, are also verified.

 

The constructor with a single parameter is never called. The first function to be called is the no parameter constructor of the zzz class. This is followed by the function InitClass, which in turn creates an object like CustomersDataTable, whose constructor gets called next.

 

Here, the InitClass function is called again, which in turn creates two DataColumn objects. These functions are called by the constructor of the zzz class.

 

The next set of functions is called by the Fill function. The function GetRowType gets called, which returns the type of the row that exists in the Table. In our case, the typeof function that returns the type of row is CustomersRow, which has two columns.

 

Thereafter, we encounter a series of six functions, each of which is called several times. It starts with the function NewRowFromBuilder. This function gets called whenever the builder wants a new row, having the same schema as the data table. It is but evident that all the rows are typed rows, but a new object that looks like CustomersRow is created.

 

This results in a call to the constructor of the CustomersRow class. Then, the third function to be called is OnRowChanging. The duty of this function is to raise the event RowChanging.

 

This event is fired whenever a row is being changed. The first task performed by this function is the calling of the base class function. The value of the event CustomersRowChanging is null, by reason of which the 'if' statement turns out to be false, and resultantly, no code gets executed.

 

Then, the function OnRowChanged gets called, which fires the RowChanged event. This event gets called whenever a row is modified successfully. As always, the base class event is called first and since the event CustomersRowChanged is null, the 'if' statement leads to a value of false. The above two functions get called again for reasons unknown, and the same procedure iterates again.

 

These functions get called in a loop, contingent upon the number of rows contained in the data table. In the 'foreach' loop, the Customers property gets called only once. The 'foreach' loop calls a function named GetEnumerator, which returns an object of type IEnumerator. Then, using the Rows properties, GetEnumerator returns the IEnumerator object.

 

Now, in order to return the values of the two fields, four functions are summoned. The 'get' accessor of the CustomerID property uses the indexer to return the current value. But first, the value of the CustomerIDColumn, which is a property in the CustomersDataTable class, is required. It returns a columnCustomerID value.

 

a.cs

using System;

using System.Data;

using System.Drawing;

using System.Windows.Forms;

using System.Data.SqlClient;

public class zzz : Form

{

public static void Main()

{

ccc c = new ccc();

c.DataSetName="ccc";

SqlConnection con = new SqlConnection("server=(local)\\NetSDK;uid=sa;

pwd=;database=northwind;Trusted_Connection=yes");

SqlDataAdapter Cust = new SqlDataAdapter ("Select * from Customers", con);

SqlDataAdapter Ord = new SqlDataAdapter ("Select * from Orders", con);

Cust.Fill(c, "Customers");

Ord.Fill(c, "Orders");

c.WriteXmlSchema("b.xsd");

}

}

public class ccc : DataSet

{

cus tc;

Orders to;

DataRelation r;

public ccc()

{

tc= new cus("Customers");

Tables.Add(this.tc);

to= new Orders("Orders");

Tables.Add(this.to);

r = new DataRelation("custord",tc.cID,to.oID);

Relations.Add(r);

}

}

public class cus : DataTable

{

public DataColumn cID;

public cus(string name) : base(name)

{

cID = new DataColumn("CustomerID");

Columns.Add(cID);

PrimaryKey = new System.Data.DataColumn[] {cID};

}

}

public class Orders : DataTable

{

public DataColumn oID;

public Orders(string name) : base(name)

{

oID = new DataColumn("CustomerID");

Columns.Add(oID);

}

}

 

In our book titled 'C# classes', we have devoted considerable time and effort in elucidating the data handling capabilities of the .Net world. The above program has been transshipped from that book, albeit with slight modifications. Here, we shall be explaining the program in a nutshell.

 

Firstly, an object 'c' is created, which is an instance of the class ccc and whose base class is Dataset. In the constructor, the object tc of type cus is initialized. The type cus in turn, derives from the DataTable class. The constructor is assigned the name of the table, which in this case is Customer.

 

In the constructor of cus, a DataColumn object cID is created with the column name of CustomerID. Using the Columns collection, this column is added to the DataTable. Further, the PrimaryKey property of the table is set to this newly created column of CustomerID.

 

In the same manner, an object 'to' of type Orders is created. Orders is also derived from the DataTable class. In the constructor of the Orders class, a DataColumn object, which is also called CustomerID, is created and added to the column collection. The name assigned to this DataTable is Orders.

 

The final outcome is that two tables are assigned a column named CustomerID, which is also made as the primary key in the Customers table. The two tables are added to the DataSet using the Tables Collection. 

 

Now, let us set a relationship between the two tables named tc and to. The constructor of the DataRelation class is passed three parameters:

·    The first is the name of the relationship, i.e. custord.

·    The second is the parent or the 'one' part of the relationship.

·    The third is the child or the 'many' part of the relationship.

 

The CustomerID field in the Customers table is made the parent, while the CustomerID field from the Orders table is made the child. Thus, for every CustomerID in the Customers table, there could exist multiple CustomerID records in the child. This Relation object is then added to the Relations collection.

 

Bear in mind that all this action takes place in the constructor of the ccc class. The DataSet object is assigned the name ccc and then, a connection object to SQLServer and the database called Northwind is created. Then, using two SqlDataAdapter objects named Cust and Ord, all the records from the Customers and Orders tables that are present in the Northwind database, are associated with each other.

 

The Fill function fills up the dataset object c with records from the Customers and Orders tables, which are present in SQLServer. The WriteXmlSchema function is then used to write out only the Schema in the file b.XSD. The data part can be safely disregarded here.

 

b.xsd

<?xml version="1.0" standalone="yes"?>

<xs:schema id="ccc" xmlns="" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">

<xs:element name="ccc" msdata:IsDataSet="true">

<xs:complexType>

<xs:choice maxOccurs="unbounded">

<xs:element name="Customers">

<xs:complexType>

<xs:sequence>

<xs:element name="CustomerID" type="xs:string" />

<xs:element name="CompanyName" type="xs:string" minOccurs="0" />

<xs:element name="ContactName" type="xs:string" minOccurs="0" />

<xs:element name="ContactTitle" type="xs:string" minOccurs="0" />

<xs:element name="Address" type="xs:string" minOccurs="0" />

<xs:element name="City" type="xs:string" minOccurs="0" />

<xs:element name="Region" type="xs:string" minOccurs="0" />

<xs:element name="PostalCode" type="xs:string" minOccurs="0" />

<xs:element name="Country" type="xs:string" minOccurs="0" />

<xs:element name="Phone" type="xs:string" minOccurs="0" />

<xs:element name="Fax" type="xs:string" minOccurs="0" />

</xs:sequence>

</xs:complexType>

</xs:element>

<xs:element name="Orders">

<xs:complexType>

<xs:sequence>

<xs:element name="CustomerID" type="xs:string" minOccurs="0" />

<xs:element name="OrderID" type="xs:int" minOccurs="0" />

<xs:element name="EmployeeID" type="xs:int" minOccurs="0" />

<xs:element name="OrderDate" type="xs:dateTime" minOccurs="0" />

<xs:element name="RequiredDate" type="xs:dateTime" minOccurs="0" />

<xs:element name="ShippedDate" type="xs:dateTime" minOccurs="0" />

<xs:element name="ShipVia" type="xs:int" minOccurs="0" />

<xs:element name="Freight" type="xs:decimal" minOccurs="0" />

<xs:element name="ShipName" type="xs:string" minOccurs="0" />

<xs:element name="ShipAddress" type="xs:string" minOccurs="0" />

<xs:element name="ShipCity" type="xs:string" minOccurs="0" />

<xs:element name="ShipRegion" type="xs:string" minOccurs="0" />

<xs:element name="ShipPostalCode" type="xs:string" minOccurs="0" />

<xs:element name="ShipCountry" type="xs:string" minOccurs="0" />

</xs:sequence>

</xs:complexType>

</xs:element>

</xs:choice>

</xs:complexType>

<xs:unique name="Constraint1" msdata:PrimaryKey="true">

<xs:selector xpath=".//Customers" />

<xs:field xpath="CustomerID" />

</xs:unique>

<xs:keyref name="custord" refer="Constraint1">

<xs:selector xpath=".//Orders" />

<xs:field xpath="CustomerID" />

</xs:keyref>

</xs:element>

</xs:schema>

 

The schema file b.xsd unearths nothing original in the schema element. The dataset is named ccc due to the element ccc, and the IsDataSet attribute is set to true. A table called Customers is created in the data set. Therefore, it becomes the first element called Customers containing the complexType of the dataset ccc.

 

The element Customers is followed by an anonymous complexType, which lists out all the fields of the Customers table in sequence. The table in the Customers dataset has one DataColumn.

 

The child elements depicted here originate from the table called Customers, which is present in the database. Since the CustomerID field is unique, the minOccurs remains at its default value of 1, whereas the other elements have a minOccurs value of 0, due to which, defaults are permissible.

 

The second table is called Orders. Therefore, the element is followed by the fields of the Orders table in the SQLServer. All the elements have a minOccurs value of zero. The first complexType is followed by a choice element having a maxOccurs value of 'unbounded'.

 

The element unique is named as Constarint1, which is a name generated by the system. The PrimaryKey attribute is set to true since the Primary key is set to the column CustomerID in the Customers Table. This section of the xsd file has been added to the DataSet. It does not have any association with the dataset.

 

The xpath attribute in the selector element points to the Customers element. Here, the field that is unique or is a primary key, is specified by the files element and the xpath attribute. The keyref element is used to specify the DataRelation class.

 

The Name attribute has a value that is specified in the DataRelation constructor. The primary key is the 'refer' attribute, which has the value of Constraint1 or the field CustomerID in the Customers table. In the selector element, the xpath points to the child table or orders, while the field element uses the xpath attribute to point to the child column CustomerID.

 

The above xsd file is created with the function WriteXmlSchema, which uses the tables in the database and the relational information provided in the file.

 

>xsd.exe /d b.xsd

 

The XSD program is then used to generate a C# program named b.cs from the file b.xsd. For all that the XSD program cares, this file could have even been written by hand. Since the file b.cs is in excess of 1000 lines, we have every reason to eschew displaying t.

 

We would advise you to scan through the code with a fine comb, to be able to understand the classes that get generated. The vital point not to be missed here is that the xsd file need not necessarily be generated by a program. Instead, even we can create it ourselves.

Therefore, it is entirely your prerogative to determine  whether the relationship is to be built using the data relation class, and also whether you want a program to write the xsd file for you, or you wish to write it yourself manually.

 

The classes in the file b.cs are then compiled to transform the file into a library file named b.dll, using the following command

 

 >csc.exe /t:library b.cs

 

a1.cs

using System;

using System.ComponentModel;

using System.Drawing;

using System.Windows.Forms;

using System.Data;

using System.Data.SqlClient;

public class zzz : Form

{

ccc c;

DataGrid d;

public zzz()

{

d = new System.Windows.Forms.DataGrid();

c = new ccc();

d.DataSource = c;

d.DataMember = "Customers";

c.DataSetName = "CustomersDataSet";

d.Size = new System.Drawing.Size(784, 536);

Controls.AddRange(new System.Windows.Forms.Control[] {d});

SqlConnection con = new SqlConnection("server=(local)\\NetSDK;uid=sa;pwd=;database=northwind; Trusted_Connection=yes");

SqlDataAdapter c1 = new SqlDataAdapter("Select * from Customers", con);

SqlDataAdapter c2 = new SqlDataAdapter("Select * from Orders", con);

c1.Fill(c, "Customers");

c2.Fill(c, "Orders");

}

public static void Main()

{

Application.Run(new zzz());

}

}

 

>csc.exe a1.cs /r:b.dll

>a1

 

In a1.cs, it is the object 'c' of type ccc that is created first. This type is the name of the Dataset in the xsd file. Then, a new instance of the DataGrid object d is created, and some of the properties are initialized to specific values.

 

The first such property is the DataSource property, which not only sets the DataSet to the tables, but also establishes a relationship amongst the tables.

 

The next member is the DataMember property that is set to Customers, in order to initially display a list of records from the Customers table. Like before, the same SQL statements are used to retrieve data, and the Fill function is employed to populate the DataSet object 'c'.

 

On running the above program named a1, a list of customers is discernible with a plus sign preceding every record.

Clicking on the plus sign results in the display of the name of the relationship object called custord. The relation is a hyperlink, which on being clicked, displays the records from the Orders table. These records contain the orders placed by the above customer.

 

Observe that a relationship between the Customers table and the Orders tables is not specified anywhere in the DataGrid. This information is stored in the class ccc. Thus, on using the XSD program, only the xsd file gets created. The classes generated by the program carry the internal relationships along with them.