22.3.5 构建模式
第21章说明了如何定义XSD模式,Visual Studio .NET有一个编辑器,它可以创建XSD模式—— 从Project菜单中选择Add New Item,再从Data目录中选择XML Schema选项,就可以访问这个编辑器,如图22-25所示。把模式命名为TestSchema.xsd。

图 22-25
这将给项目添加两个新文件:.xsd文件和一个对应的.xsx文件(它由设计器用于为已设计的模式元素存储布局信息)。要为模式创建相应的一组代码,可从Schema中选择Generate Dataset选项,如图22-26所示。

图 22-26
选择这个选项会给项目添加一个额外的C#文件,它也显示在Solution Explorer中XSD文件的下面。当对XSD模式进行修改时,这个文件会自动生成,所以不应手工编辑它。该文件是用sxd.exe工具生成的。
Visual Studio .NET编辑器有XSD文件的两个视图:Schema视图和XML视图。单击XML标签,就会看到最初的模式模板:
<?xml version="1.0" encoding="utf-8" ?>
<xs:schema id="TestSchema"
targetNamespace="http://tempuri.org/TestSchema.xsd"
elementFormDefault="qualified"
xmlns="http://tempuri.org/TestSchema.xsd"
xmlns:mstns="http://tempuri.org/TestSchema.xsd"
xmlns:xs="http://www.w3.org/2001/XMLSchema">
</xs:schema>
这个XSD脚本在TestSchema.cs文件中生成下述C#代码。在下面的代码中,省略了方法的主体和格式化的内容,以提高可读性。您自己在建立这个示例时,就可以看到生成的代码:
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 TestSchema : DataSet
{
public TestSchema() { ... }
protected TestSchema(SerializationInfo info, StreamingContext context)
{ ... }
public override DataSet Clone() { ... }
protected override bool ShouldSerializeTables() { ... }
protected override bool ShouldSerializeRelations() { ... }
protected override void ReadXmlSerializable(XmlReader reader) { ... }
protected override System.Xml.Schema.XmlSchema GetSchemaSerializable()
{ ... }
internal void InitVars() { ... }
private void InitClass() { ... }
private void SchemaChanged(object sender,
System.ComponentModel.CollectionChangeEventArgs e)
{ ... }
}
把这段代码作为本节的开始,您就可以看到在给XSD模式添加元素后,代码所发生的变化。要注意的两个问题是:XSD模式映射为DataSet,这个DataSet是可以串行化的—— 注意受保护的构造函数可以由ISerializable实现使用。串行化的内容详见第11章。
1. 添加元素
要添加一个新的顶层元素,右击工作空间,从关联菜单中选择Add | New Element。这将在屏幕上创建一个未命名的新元素。图22-27显示了为这个示例的Product元素设置的属性。

图 22-27
在保存XSD文件时,可以修改该C#文件,并且生成许多新类,如下面的代码所示。下一节将讨论在文件TestSchema.cs中生成的代码的最重要的方面。
public class TestSchema : DataSet
{
private ProductDataTable tableProduct;
[System.ComponentModel.DesignerSerializationVisibilityAttribute
(System.ComponentModel.DesignerSerializationVisibility.Content)]
public ProductDataTable Product
{
get
{
return this.tableProduct;
}
}
}
创建了ProductTable类的一个新成员变量,这个对象由Product属性返回,并在更新的InitClass()方法中构造。在这一小段代码中,使用这些类可以从这个文件的类中构造一个DataSet,使用DataSet.Products返回Products数据表。
2. 生成的数据表
为添加到模式模板中的DataTable (Product)生成下面的代码:
public delegate void ProductRowChangeEventHandler
(object sender, ProductRowChangeEvent e);
public class ProductDataTable : DataTable, System.Collections.IEnumerable
{
internal ProductDataTable() : base("Product")
{
this.InitClass();
}
[System.ComponentModel.Browsable(false)]
public int Count
{
get { return this.Rows.Count;}
}
public ProductRow this[int index]
{
get { return ((ProductRow)(this.Rows[index]));}
}
public event ProductRowChangeEventHandler ProductRowChanged;
public event ProductRowChangeEventHandler ProductRowChanging;
public event ProductRowChangeEventHandler ProductRowDeleted;
public event ProductRowChangeEventHandler ProductRowDeleting;
生成的ProductDataTable类派生于DataTable,其中包括IEnumerable接口的实现。定义了4个事件,当引发这些事件时,会使用上面定义的委托,这个委托的参数是ProductRowChangeEvent类的一个实例,该类也是由Visual Studio .NET定义的。
生成的代码包含一个派生于DataRow的类,它可以对表中的类进行类型安全的访问。创建新行有两种方式:
● 调用NewRow (或生成的NewProductRow)方法,返回行类的一个新实例。把这个新行传递给 AddRow()函数(或类型安全的AddProductRow)。
● 调用AddRow (或生成的AddProductRow)方法,其参数是一个对象数组,数组中的元素对应于表中的每一列。
AddProductRow方法如下所示:
public void AddProductRow(ProductRow row)
{
this.Rows.Add(row);
}
public ProductRow AddProductRow ( ... )
{
ProductRow rowProductRow = ((ProductRow)(this.NewRow()));
rowProductRow.ItemArray = new Object[0];
this.Rows.Add(rowProductRow);
return rowProductRow;
}
从代码中可以看出,第二个方法创建了一个新行,把该行插入到DataTable的Rows集合中,再把该对象返回给调用者。DataTable上的其他方法用于引发事件。
3. 生成的数据行
生成的 ProductRow 类如下所示:
public class ProductRow : DataRow
{
private ProductDataTable tableProduct;
internal ProductRow(DataRowBuilder rb) : base(rb)
{
this.tableProduct = ((ProductDataTable)(this.Table));
}
public string Name { ... }
public bool IsNameNull { ... }
public void SetNameNull { ... }
// Other accessors/mutators omitted for clarity
}
把属性添加到元素上时,就会把一个特性添加到生成的DataRow类上,如上述代码所示。该特性与属性有相同的名称,所以在上面的示例中,Product行包含Name、SKU、Description和Price特性。
对于每个新添加的属性,都对.cs文件进行了几处修改。在下面的示例中,假定添加了一个类型为int的属性ProductId。
首先给ProductDataTable类(派生于DataTable)添加一个私有成员,即新的DataColumn:
private DataColumn columnProductId;
用属性ProductIDColumn来连接,这个属性被定义为internal:
internal DataColumn ProductIdColumn
{
get { return this.columnProductId; }
}
上面的AddProductRow()方法也做了修改,该函数现在带有一个整型参数ProductID,并存储了在新建列中输入的值:
public ProductRow AddProductRow ( ... , int ProductId)
{
ProductRow rowProductRow = ((ProductRow)(this.NewRow()));
rowProductRow.ItemArray = new Object[] { ... , ProductId};
this.Rows.Add(rowProductRow);
return rowProductRow;
}
最后在ProductDataTable中修改InitClass()方法:
private void InitClass()
{
...
this.columnProductID = new DataColumn("ProductID", typeof(int), null,
System.Data.MappingType.Attribute);
this.Columns.Add(this.columnProductID);
this.columnProductID.Namespace = "";
}
这段代码创建了新的DataColumn,并把它添加到DataTable的Columns集合中。DataColumn构造函数的最后一个参数定义了在把DataSet保存到XML文件中时,该列如何映射回XML上。
更新ProductRow类,并给这个列添加一个访问器:
public int ProductId
{
get { return ((int)(this[this.tableProduct.ProductIdColumn])); }
set { this[this.tableProduct.ProductIdColumn] = value; }
}
4. 生成的EventArgs
最后一个添加到源代码中的类是EventArgs的一个派生类,它提供的方法可以直接访问已经改变(或者正在改变)的行,以及应用到该行上的操作。这里为了简洁起见,省略了这段代码。





