21.6 XML模式
XML已经在ADO.NET确立了牢固的地位—— 实际上,现在在对象之间远程传递数据的格式是XML。有了.NET运行库,就可以在XML模式定义文件(XSD)中描述DataTable了。而且,可以定义整个DataSet,其中有许多DataTables,这些表之间有一定的关系,并包括描述数据的其他信息。
在定义XSD文件时,运行库中有一个新工具,该工具可以把这个模式转换为对应的数据访问类,例如类型安全的产品DataTable类,如上所述。本节开始介绍一个简单的XSD文件,该文件描述与前面产品示例相同的信息,再扩展它使其包括一些额外的功能。
<?xml version="1.0" encoding="utf-8" ?>
<xs:schema
id="Products"
targetNamespace="http://tempuri.org/XMLSchema1.xsd"
xmlns:mstns="http://tempuri.org/XMLSchema1.xsd"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xs:element FTEL="Product">
<xs:complexType>
<xs:sequence>
<xs:element FTEL="ProductID" msdata:ReadOnly="true"
msdata:AutoIncrement="true" type="xs:int" />
<xs:element FTEL="ProductName" type="xs:string" />
<xs:element FTEL="SupplierID" type="xs:int" minOccurs="0" />
<xs:element FTEL="CategoryID" type="xs:int" minOccurs="0" />
<xs:element FTEL="QuantityPerUnit" type="xs:string" minOccurs="0" />
<xs:element FTEL="UnitPrice" type="xs:decimal" minOccurs="0" />
<xs:element FTEL="UnitsInStock" type="xs:short" minOccurs="0" />
<xs:element FTEL="UnitsOnOrder" type="xs:short" minOccurs="0" />
<xs:element FTEL="ReorderLevel" type="xs:short" minOccurs="0" />
<xs:element FTEL="Discontinued" type="xs:boolean" />
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:schema>
第24章将详细论述其中的一些选项。现在应知道,这个文件定义一个带有id属性的模式,其中把id属性设置为Products。还定义了一个比较复杂的类Product,其中包含了许多元素,每个元素对应于Products表中的一个字段。
这些元素映射到数据类上。其中Products模式映射到派生于DataSet的一个类上,类Product映射到派生于DataTable的一个类上。每个子元素映射到派生于DataColumn的一个类上。所有列的集合都映射到派生于DataRow的一个类上。
.NET Framework中有一个工具,只要输入XSD文件,该工具就可以生成这些类的代码。因为该工具惟一的工作是执行XSD文件上的各种功能,所以它称为XSD.EXE。
用XSD生成代码
假定把上面的文件保存为Product.xsd,在命令提示符上输入下述命令,把该文件转换为 代码:
xsd Product.xsd /d
这会创建文件Product.cs。
该命令有许多选项可以和XSD一起使用,以改变生成的输出结果,其中一些比较常用的选项如表21-8所示。
表 21-8
|
选 项 |
说 明 |
|
/dataset (/d) |
生成派生于DataSet、DataTable和DataRow的类 |
|
/language:<language> |
允许选择编写输出文件的语言。C#是默认语言,也可以选择用VB编写Visual Basic文件 |
|
/namespace:<namespace> |
定义存储生成代码的命名空间,默认为没有命名空间 |
下面节选了products模式在XSD中的输出结果,并做了略微的修改,重新进行了格式化,以便可以放在本书中。要查看完整的输出结果,可以在products模式(或者自己建立的某个模式)上运行XSD.EXE,查看生成的.cs文件。该示例包含了所有的源代码和Products.XSD文件,这个输出结果是www.wroc.com上下载代码文件的一部分。
//------------------------------------------------------------------------------
// <autogenerated>
// This code was generated by a tool.
// Runtime Version: 11.4322.573
//
// 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=11.4322.573
//
using System;
using System.Data;
using System.Xml;
using System.Runtime.Serialization;
[Serializable()]
[System.ComponentModel.DesignerCategoryAttribute("code")]
[System.Diagnostics.DebuggerStepThrough()]
[System.ComponentModel.ToolboxItem(true)]
public class Products : DataSet
{
private ProductDataTable tableProduct;
public Products()
public ProductDataTable Product
public override DataSet Clone()
public delegate void ProductRowChangeEventHandler ( object sender,
ProductRowChangeEvent e);
[System.Diagnostics.DebuggerStepThrough()]
public class ProductDataTable : DataTable, System.Collections.IEnumerable
[System.Diagnostics.DebuggerStepThrough()]
public class ProductRow : DataRow
}
为了集中论述公共接口,这段代码删除了所有受保护和私有的成员。ProductDataTable和ProductRow定义是两个嵌套的类,后面会介绍它们。下面简单地解释一下DataSet派生类,之后讨论这些类的代码。
Products()构造函数调用一个私有方法InitClass(),构造派生于DataTable类的ProductDataTable的一个实例,把该表添加到DataSet的Tables集合上。Products数据表可以通过下面的代码来访问:
DataSet ds = new Products();
DataTable products = ds.Tables["Products"];
更简单的方式是使用Product属性来访问,该属性在派生的DataSet对象上:
DataTable products = ds.Product;
因为Product属性是强类型化的,所以可以使用ProductDataTable,而不是上面的DataTable引用。
ProductDataTable类包含更多的代码:
[System.Diagnostics.DebuggerStepThrough()]
public class ProductDataTable : DataTable, System.Collections.IEnumerable
{
private DataColumn columnProductID;
private DataColumn columnProductName;
private DataColumn columnSupplierID;
private DataColumn columnCategoryID;
private DataColumn columnQuantityPerUnit;
private DataColumn columnUnitPrice;
private DataColumn columnUnitsInStock;
private DataColumn columnUnitsOnOrder;
private DataColumn columnReorderLevel;
private DataColumn columnDiscontinued;
internal ProductDataTable() : base("Product")
{
this.InitClass();
}
ProductDataTable类派生于DataTable,它实现IEnumerable接口,为表中的每一列定义了一个私有的DataColumn实例,通过调用私有的InitClass成员,在构造函数中初始化这些实例。每一列都有一个内部的访问器,DataRow类将使用这个访问器(后面介绍)。
[System.ComponentModel.Browsable(false)]
public int Count
{
get { return this.Rows.Count; }
}
internal DataColumn ProductIDColumn
{
get { return this.columnProductID; }
}
// Other row accessors removed for clarity – there is one for each of the columns
给表添加数据行是由AddProductRow()的两个重载方法实现的(但它们的内容完全不同,但名称相同)。第一个重载方法的参数是一个已经构造出来的DataRow,且没有返回值。另一个重载方法则带一组参数值,每个参数对应于DataTable中的一列,该重载方法构造一个新行,设置这个新行中的值,把该行添加到DataTable,并给调用者返回该行。这些不同的函数不应有相同的名称。
public void AddProductRow(ProductRow row)
{
this.Rows.Add(row);
}
public ProductRow AddProductRow ( string ProductName , int SupplierID ,
int CategoryID , string QuantityPerUnit ,
System.Decimal UnitPrice , short UnitsInStock ,
short UnitsOnOrder , short ReorderLevel ,
bool Discontinued )
{
ProductRow rowProductRow = ((ProductRow)(this.NewRow()));
rowProductRow.ItemArray = new object[]
{
null,
ProductName,
SupplierID,
CategoryID,
QuantityPerUnit,
UnitPrice,
UnitsInStock,
UnitsOnOrder,
ReorderLevel,
Discontinued
};
this.Rows.Add(rowProductRow);
return rowProductRow;
}
DataSet派生类中的InitClass成员把表添加到DataSet,与此相同,ProductDataTable上的InitClass成员把列添加到DataTable中,每一列的属性都按需要设置,再把该列添加到列集合的尾部。
private void InitClass()
{
this.columnProductID = new DataColumn ( "ProductID",
typeof(int),
null,
System.Data.MappingType.Element);
this.Columns.Add(this.columnProductID);
// Other columns removed for clarity
this.columnProductID.AutoIncrement = true;
this.columnProductID.AllowDBNull = false;
this.columnProductID.ReadOnly = true;
this.columnProductName.AllowDBNull = false;
this.columnDiscontinued.AllowDBNull = false;
}
public ProductRow NewProductRow()
{
return ((ProductRow)(this.NewRow()));
}
NewRowFromBuilder在DataTable的 NewRow()方法内部调用,创建了一个强类型化的新行。DataRowBuilder实例由DataTable创建,其成员仅能在System.Data程序集中访问。
protected override DataRow NewRowFromBuilder(DataRowBuilder builder)
{
return new ProductRow(builder);
}
最后一个要讨论的类是ProductRow,它派生于DataRow。这个类用于提供对数据表中所有字段的类型安全的访问。它封装了特定行的存储器,并提供成员来读取(和写入)表中的每个字段。
另外,对于每个可为空的字段,还有函数可以把该字段设置为null,并检查该字段是否为null。下面的示例列出了SupplierID列的函数:
[System.Diagnostics.DebuggerStepThrough()]
public class ProductRow : DataRow
{
private ProductDataTable tableProduct;
internal ProductRow(DataRowBuilder rb) : base(rb)
{
this.tableProduct = ((ProductDataTable)(this.Table));
}
public int ProductID
{
get { return ((int)(this[this.tableProduct.ProductIDColumn])); }
set { this[this.tableProduct.ProductIDColumn] = value; }
}
// Other column accessors/mutators removed for clarity
public bool IsSupplierIDNull()
{
return this.IsNull(this.tableProduct.SupplierIDColumn);
}
public void SetSupplierIDNull()
{
this[this.tableProduct.SupplierIDColumn] = System.Convert.DBNull;
}
}
下面的代码利用XSD工具中的类的输出从Product表中检索数据,并在控制台上显示这些数据:
using System;
using System.Data;
using System.Data.SqlClient;
public class XSD_DataSet
{
public static void Main()
{
string source = "server=(local)\\NetSDK;" +
"uid=QSUser;pwd=QSPassword;" +
"database=northwind";
string select = "SELECT * FROM Products";
SqlConnection conn = new SqlConnection(source);
SqlDataAdapter da = new SqlDataAdapter(select , conn);
Products ds = new Products();
da.Fill(ds , "Product");
foreach(Products.ProductRow row in ds.Product )
Console.WriteLine("'{0}' from {1}" ,
row.ProductID ,
row.ProductName);
}
}
重要的代码已突出显示出来了。XSD文件的输出结果包含一个派生于DataSet的类Products,使用数据适配器来创建和填充这个类。foreach语句使用了强类型化的ProductRow和Product属性,返回Product数据表。
要编译这个示例,执行下面的命令:
xsd product.xsd /d
和
csc /recurse:*.cs
第一个命令从Products.XSD模式中生成Products.cs文件,然后,csc命令使用/recurse:*.cs参数查找扩展名为.cs的文件,并把它们添加到所生成的程序集中。





