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.