应该感谢那些指出你错误的人

借我三千虎骑,复我泱泱中华!

博客园 首页 新随笔 联系 订阅 管理

如今有许多查看和使用数据的方法。传统上,我们使用诸如 ADO.NET 这样的数据访问技术,这种技术为我们提供了主要针对关系数据的强大功能。利用 ADO 和 ADO.NET,可以实现与非关系数据的交互,但真正的焦点是传统的表格式数据视图。最近,我们看到了 XML 的崛起,这种技术的核心是分层数据表示形式。

多年以前,在我接触到关系数据库之前,我使用的是层次数据库。在许多方面,XML 可以说是层次数据库概念的开放表示形式,但因为 XML 是基于文本的,能够利用 XSL 应用转换,再加上其他与 XML 相关的标准,它在可扩展性和表现能力方面远远超出层次数据库。

利用 ADO.NET,Microsoft 不仅提供对关系数据源中的传统表格式数据视图的支持,而且还集成了对 XML 的强大支持。ADO.NET 和 XML 之间的关系以若干个级别存在于基础结构中,因此我们可以基于自己的应用程序要求以灵活的方式使用这两种数据技术。

在本文中,我将讨论 ADO.NET 与 XML 配合使用的基本知识。

*

ADO.NET 和 XML 的结构

在编写代码之前,我希望快速回顾一下 ADO.NET 与 XML 集成的方式。然后,我们通过构建一个说明这两种技术如何配合使用的测试应用程序,在实践中应用概念。

ADO.NET 本身可分为两个部分。一部分是托管提供程序,负责处理与数据源的通讯。另一部分是数据集,为表格式数据提供内存中的存储区。数据集可以加载来自托管提供程序的数据,或者可以直接从我们的代码加载。另外,数据集也可以通过使用 XmlReader 加载数据。上述内容如图 1 所示。

向数据集加载数据的三种方式。

1. 向数据集加载数据的三种方式。

无论使用哪种方式,最终结果都是包含数据表的数据集,而数据表又包含我们已经加载的数据。数据表是表格式结构,因此数据被表示为一组行和列。

利用数据集,我们可以通过代码操纵数据,或者,我们可以使用数据绑定将数据连接到用户界面 (UI) 中的控件。一旦数据填充到数据集,无论数据来自 Microsoft SQL Server? 还是 XML 文档,在行为上都没有什么不同。这一特点可以实现很强大的功能,因为它意味着,我们可以从表加载数据,然后将数据保存到 XML 文件,我们也可以从 XML 文件加载数据,然后将数据存储到数据库中的表。

使用 XmlReader 方法用 XML 填充数据集时,它将自动为 XML 数据生成一个定义,即 XSD。XSD 是在 XML 文档被读取时从它的结构派生出来的。这一点非常有用,因为它带来了另外一个好处,即您可以使用数据集迅速生成从现有的 XML 文档派生的 XSD。我很喜欢这个随带的好处,因为手动编写 XSD 是我最不喜欢做的事情之一。

另一方面,如果您希望控制从 XML 文件读到数据集中的字段,您可以创建自己的 XSD,在读取 XML 数据之前用它来初始化数据集。然后,在读取 XML 数据时,只有与 XSD 字段相匹配的数据被读入数据集。XML 文档中的任何其他数据都将被忽略。

需要说明的是 — 如果您读取数据时使用自定义的 XSD,数据集将只包含由该 XSD 定义的数据。如果您后来又将数据集另存为 XML,则只保留该 XSD 定义的数据。原来的 XML 文件中的任何其他数据都不会包括在输出中。

还有一点需要注意,当直接从数据集使用 XML 时,无论有没有预定义的 XSD,数据集不保留 XML 文档的格式设置。XML 文档内的任何空格、注释或其他非数据元素,都将在数据集写出 XML 时丢失。

为了解决数据丢失、格式设置和注释丢失这些问题,ADO.NET 提供了另一种使用 XML 的机制。

如果您以前曾经在 .NET 中使用过 XML,您一定很熟悉 XmlDocument 对象。XmlDocument 读取 XML 文档,允许我们对它进行操作,然后可以将它保存回去。该对象可以保留格式设置、注释和所有数据。

Microsoft .NET 还包括 XmlDataDocument 对象。XmlDataDocument 具有 XmlDocument 的所有功能,此外,它能理解如何与 ADO.NET 数据集集成。如图 2 所示。

XmlDataDocument 与 ADO.NET 数据集集成。

2. XmlDataDocument 与 ADO.NET 数据集集成。

这意味着我们可以同时使用数据集以表格格式和通过 XmlDataDocument 以分层格式查看数据。由于 XSD 定义了可以使用数据集的特定数据,因此我们可以准确地控制 XmlDataDocument 中的哪些数据可以通过数据集提供。

通过数据集视图,我们可以利用数据绑定的功能,使熟悉使用表格式数据的广大开发人员可以访问 XML 数据。XmlDataDocument 视图允许我们操作所有 XML 数据,包括数据集中不提供的任何数据。另外,XmlDataDocument 继续保留空格、格式设置和注释,因此当我们将文档保存回去的时候,我们将得到与读取时相比,属于同一类别的文件。

XmlDataDocument 和数据集之间的集成是双向的。一旦它们链接在一起,对数据集中的数据的任何更新将立即反映在 XmlDataDocument 中。同样,对 XmlDataDocument 中的数据的任何更改也将立即反映在数据集中。

除了能够直接读取 XML、将 XML 写到数据集中,以及能够将XmlDataDocument与数据集链接在一起,ADO.NET 还提供了强大的 XML 支持。了解上述背景后,我们可以研究并编写一些代码来说明 ADO.NET 如何与 XML 配合使用。

我们将对基于专栏文章ADO.NET and You中的应用程序进行构建。您可以从 MSDN 下载该代码。

该应用程序已经有一个 DataGrid、一个到 Pubs 数据库的连接,并且能够检索和更新 Authors 表中的数据。该应用程序还有一些按钮,您可以用它们将表中的数据写到文件,并将该文件读回数据库表。此示例中的文件不是 XML,而是简单文本文件。我们将对该应用程序进行扩展,使之使用 XML 文件。

直接在数据集中使用 XML

DataSet 对象包括直接读写 XML 文件的方法。这些方法的一个优点是,我们可以从数据库加载数据,然后将数据写到 XML 文件。现在我们来增强应用程序,以便利用这些方法。

将 XML 保存到文件

将一个按钮添加到名为 btnToXML 的 Form1。我们将编写此按钮后面的代码,将当前在 dsPubs 数据集中的任何数据保存为 XML 文件:

Private Sub btnToXML_Click(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles btnToXML.Click
DsPubs.WriteXml("c:\authors.xml")
End Sub

XML 将基于数据表中的列名自动生成,并且将包括数据集中的所有数据。如果数据集有一个以上的DataTable 对象,数据将包括在输出中。假设我们已经将 Authors 表中的数据加载到数据集,单击此按钮即可生成其内容如下所示的文件:

<?xml version="1.0" standalone="yes"?>
<dsPubs xmlns="http://www.tempuri.org/dsPubs.xsd">
<authors>
<au_id>172-32-1176</au_id>
<au_lname>White</au_lname>
<au_fname>Gerry</au_fname>
<phone>408 496-7223</phone>
</authors>
<authors>
<au_id>213-46-8915</au_id>
<au_lname>Green</au_lname>
<au_fname>Marjorie</au_fname>
<phone>415 986-7020</phone>
</authors>
</dsPubs>

数据集中的所有数据都会包括在内。从该 XML 可以大致看出输出的模式。XML 简单而直接,没有特定于数据集的信息或 XSD。如果我们需要数据的 XSD,可以通过下面的更改指定 XSD:

Private Sub btnToXML_Click(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles btnToXML.Click
DsPubs.WriteXml("c:\authors.xml")
DsPubs.WriteXml("c:\authors with schema.xml", _
XmlWriteMode.WriteSchema
End Sub

现在我们创建了两个文件。第一个文件包含简单的 XML 格式的数据,第二个文件包含以相同格式呈现的相同数据,但前面带有定义了数据结构的 XSD 信息。

通常,数据集自动生成 XSD。另一方面,我们也可以定义自己的 XSD,并在加载数据之前将它加载到数据集。在这种情况下,此 XSD 将反映我们预先确定的定义。稍后将研究加载自己的 XSD。

在我们的示例中,dsPubs 的 XSD 是我们在创建DataSet 对象时由 DataAdapter 向导自动生成的。以下是 XSD 的具体内容(部分文本已换行):

<?xml version="1.0" standalone="yes"?>
<dsPubs xmlns="http://www.tempuri.org/dsPubs.xsd">
<xs:schema id="dsPubs"
targetNamespace="http://www.tempuri.org/dsPubs.xsd"
xmlns:mstns="http://www.tempuri.org/dsPubs.xsd"
xmlns="http://www.tempuri.org/dsPubs.xsd"
xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-
microsoft-com:xml-msdata" attributeFormDefault="qualified"
elementFormDefault="qualified">
<xs:element name="dsPubs" msdata:IsDataSet="true">
<xs:complexType>
<xs:choice maxOccurs="unbounded">
<xs:element name="authors">
<xs:complexType>
<xs:sequence>
<xs:element name="au_id" type="xs:string" />
<xs:element name="au_lname" type="xs:string" />
<xs:element name="au_fname" type="xs:string" />
<xs:element name="phone" type="xs:string" />
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:choice>
</xs:complexType>
<xs:unique name="Constraint1" msdata:PrimaryKey="true">
<xs:selector xpath=".//mstns:authors" />
<xs:field xpath="mstns:au_id" />
</xs:unique>
</xs:element>
</xs:schema>

Author 数据紧跟在 XSD 的后面。注意,XSD 知道唯一主键值。这是因为 DataAdapter 向导是基于数据库定义生成 XSD 的。而从原始数据派生的 XSD 则不会包括这样的高级概念。

例如,如果您从 authors.xml 将 XML 加载到一般的 DataSet 对象,然后通过如下代码写出 XML(不输入此代码):

Dim dsXML As New DataSet()
dsXML.ReadXml("c:\authors.xml")
dsXML.WriteXml("c:\authors with schema.xml", _
XmlWriteMode.WriteSchema)

得到的 XSD 会更简单(部分文本已换行):

<?xml version="1.0" standalone="yes"?>
<dsPubs xmlns="http://www.tempuri.org/dsPubs.xsd">
<xs:schema id="dsPubs"
targetNamespace="http://www.tempuri.org/dsPubs.xsd"
xmlns:mstns="http://www.tempuri.org/dsPubs.xsd"
xmlns="http://www.tempuri.org/dsPubs.xsd"
xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-
microsoft-com:xml-msdata" attributeFormDefault="qualified"
elementFormDefault="qualified">
<xs:element name="dsPubs" msdata:IsDataSet="true">
<xs:complexType>
<xs:choice maxOccurs="unbounded">
<xs:element name="authors">
<xs:complexType>
<xs:sequence>
<xs:element name="au_id" type="xs:string" minOccurs="0" />
<xs:element name="au_lname" type="xs:string"
minOccurs="0" />
<xs:element name="au_fname" type="xs:string"
minOccurs="0" />
<xs:element name="phone" type="xs:string" minOccurs="0" />
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:choice>
</xs:complexType>
</xs:element>
</xs:schema>

这里再次提醒大家,Author 数据紧跟在 XSD 的后面。

注意,前面没有提到主键。这类信息是无法从简单的 XML 数据派生的。这种 XSD 只包括可以直接从数据本身推断出的基本信息。 我们可以增强代码的功能,不仅能够编写出 XML 及具有架构的 XML 的代码,还可以编写出架构本身的代码:

Private Sub btnToXML_Click(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles btnToXML.Click
DsPubs.WriteXml("c:\authors.xml")
DsPubs.WriteXml("c:\authors with schema.xml", _
XmlWriteMode.WriteSchema)
DsPubs.WriteXmlSchema("c:\authors.xsd")
End Sub

还要记住的是,由数据集编写出的 XML 和 XSD 都不包含注释或特殊格式设置,也不包含数据集中包含的数据之外的任何数据。

从文件读取 XML

将 XML 文件读到数据集的过程也同样简单。通过在窗体背后使用下面的代码,可将另一个按钮添加到名为 btnFromXML 的 Form1(窗体 1):

Private Sub btnFromXML_Click(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles btnFromXML.Click
DsPubs.Clear()
DsPubs.ReadXml("c:\authors.xml")
End Sub

ReadXml 方法从文件读取数据,然后将数据加载到数据集。

Clear 方法清除 dsPubs 中已有的任何数据,这一点很重要,因为将 XML 读到数据集与添加一组新行是一样的。由于我们使用的是利用 DataAdapter 向导生成的类型化数据集(如 dsPubs),数据集将强制执行诸如必须具有唯一主键这样的规则。因此,如果我们尝试从 XML 文件加载的数据与数据集中已有的数据相同,将导致运行时错误。

如果我们的 DataSet 对象是一般数据集,其表现将有所不同。例如,我们可以编写下面的代码(不输入此代码):

Dim dsXML As New DataSet()
dsXML.ReadXml("c:\authors.xml")
dsXML.ReadXml("c:\authors.xml")

其结果是两次包含所有数据的数据集。该数据集将自动生成数据的 XSD,但生成的 XSD 不知道唯一主键值。这样就可以多次加载相同的数据行。

使用正确的 XSD

我们已经看到数据集使用的 XML 定义(或 XSD)在行为上有很大的不同。我们的控制已超出数据集是否知道唯一主键这个问题。通过预定义数据集的 XSD,我们可以确切地控制哪些数据字段从源 XML 文件读取,从而确定哪些字段将在我们保存 XML 时被写出。

我们一直在使用的数据集 dsPubs 是使用 "ADO.NET and You" 一文中的 DataAdapter 向导生成的。这意味着它是一个类型化数据集,带有预定义的 XSD。我们可以在 Visual Studio? .NET 中使用其内置的 XSD 设计器编辑此 XSD。dsPubs.xsd 是解决方案资源管理器中的一个文件。通过双击打开设计器,如图 3 所示。

Visual Studio .NET 中的 XSD 设计器。

3. Visual Studio .NET 中的 XSD 设计器。

设计器在au_id 字段的旁边显示了一个钥匙图标,说明它就是唯一主键。设计器还列出可加载到数据集中的所有数据的字段名和数据类型。您可以使用此设计器来添加其他字段、删除字段或更改字段的数据类型。

有一点需要注意的是,当我们从SqlDataAdapter将数据加载到数据集时,这里显示的 XSD 将被忽略。举例来说,即使我们通过编辑它来删除Phone字段,当我们重新从数据库加载数据时,Phone 字段将还原,因为 XSD 将基于 SQL 定义被重新创建。

不过,当我们从 XML 文件将数据加载到数据集时,将忽略这里显示的 XSD。要了解具体过程,请在Phone 字段的左侧右击,然后从弹出式菜单中选择Delete。此操作将从 XSD 中删除Phone字段。

现在运行应用程序,然后单击From XML 按钮将 authors.xml 文件加载到数据集。注意,Phone字段没有显示在网格中,也没有加载到数据集中。该字段不匹配 XSD,因此被忽略了。

现在再回到 btnToXML 的代码,其中有一行代码将 XSD 从数据集写到一个名为 authors.xsd 的文件。Visual Studio .NET 也使用 XSD 设计器来编辑此文件。选择File | Open | File,然后选择 authors.xsd。该文件将在同样类型的设计器中打开,我们可以用图形方式编辑 XSD,或通过单击设计器底部的XML选项卡直接编辑。

为进行此演示,通过在该字段左侧右击后选择Delete,删除Phone 字段。您可能必须先用左键单击Phone 字段,来选择该字段,否则,Delete 操作将从 XSD 删除整个 Authors 实体。之后,保存该文件。

我们可以创建一个一般的数据集,并从诸如 authors.xsd 这样的文件将 XSD 加载到该数据集中。向名为 btnXSD 的 Form1 添加一个新按钮,并为一般数据集添加以下窗体级变量声明:

Private dsXML As DataSet

然后在新按钮后面加入以下代码:

Private Sub btnXSD_Click(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles btnXSD.Click
' Create a generic DataSet
dsXML = New DataSet()
' Load the XSD into the DataSet
dsXML.ReadXmlSchema("c:\authors.xsd")
' Load the XML data into the DataSet
dsXML.ReadXml("c:\authors.xml")
' Bind the grid to this new DataSet
DataGrid1.DataSource = dsXML.Tables(0)
End Sub

我们做的第一件事就是创建一个一般的DataSet对象。与具有相关联的 XSD 的类型化数据集 dsPubs 不同,这个新的数据集完全未定义,并且是空的,没有任何 XSD。

然后,我们将 XSD 文件 authors.xsd 加载到该数据集。这使得数据集拥有了数据的定义。在这种情况下,该定义知道主键字段,但由于我们删除了 Phone 字段,它并不知道该数据。

然后我们从 authors.xml 加载了 XML 数据本身。该文件的确包含 Phone 信息,但该信息被忽略了,因为它不匹配 XSD。接下来,DataGrid 被绑定到这个新的DataSet 对象,因此将显示结果。

使用自定义的 XSD 来加载数据时的数据显示。

4. 使用自定义的 XSD 来加载数据时的数据显示。

图 4 说明了为什么知道我们使用的是自动派生的 XSD(即 Visual Studio .NET 中的 DataAdapter 向导创建的 XSD),还是手动加载的 XSD 非常重要而且很有用。通过控制架构,我们可以控制加载的数据,以及数据在加载后的行为。

使用 XmlDataDocument 和数据集

到目前为止我们一直直接从数据集使用 XML。虽然这样做非常方便,而且功能相当强大,但也存在缺点。当我们混合使用 XML 数据源和非 XML 数据源时,那些技巧最有效。但我们如果读取和更新现有的 XML 文件,就会遇到限制。

尤其是,数据集只加载 XSD 架构为数据集定义的 XML 数据,忽略 XML 源中的所有其他数据。从数据集将数据写入 XML 时,只写入数据集中的数据,原来的 XML 文件中的任何其他数据将丢失。可能更糟的是,将数据读入数据集,然后再从数据集保存到 XML 文件时,原来的 XML 文件中的任何注释或特殊格式设置都会丢失。

为了克服这些限制,我们可以将XmlDataDocument 与数据集一起使用。XML 数据将加载到XmlDataDocument 对象,该对象将链接到一个数据集。我们可以直接通过XmlDataDocument 使用 XML 数据,或者,可以使用数据集以表格视图使用 XML 数据。

读取 XML 数据

在 Form1 中,为 Xml 命名空间添加Imports 语句:

Imports System.Xml

然后添加XmlDataDocument的窗体级变量声明:

Private xmlDoc As XmlDataDocument

现在,向名为 btnXMLdoc 的 Form1 添加一个按钮,该按钮将触发通过相关联的数据集将一个 XML 文档加载到XmlDataDocument 的过程:

Private Sub btnXMLdoc_Click(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles btnXMLdoc.Click
DsPubs.Clear()
xmlDoc = New XmlDataDocument(DsPubs)
xmlDoc.Load("c:\authors by hand.xml")
End Sub

首先我们要清除数据集,以确保它不包含任何数据。然后将DataSet 对象链接到一个新的XmlDataDocument 对象。此时,我们加载到XmlDataDocument的任何数据都将反映在数据集中。也就是说,是与定义数据集的 XSD 相匹配的任何数据。由于我们使用的是 dsPubs,我们知道它有一个相关联的 XSD,该 XSD 涵盖了我们一直在使用的 Author 数据。

为了了解具体过程,我们需要一个包括某些额外数据和一个注释的 XML 文档,这样就可以知道这类信息是如何保留的。 使用 Visual Studio .NET 打开 authors.xml 文件,然后编辑该文件,如下所示:

<?xml version="1.0" standalone="yes"?>
<dsPubs xmlns="http://www.tempuri.org/dsPubs.xsd">
<!-- a comment in the document -->
<authors>
<au_id>172-32-1176</au_id>
<au_lname>White</au_lname>
<au_fname>Gerry</au_fname>
<Phone>408 496-7223</Phone>
<Notes>Other data</Notes>
</authors>
<authors>
<au_id>213-46-8915</au_id>
<au_lname>Green</au_lname>
<au_fname>Marjorie</au_fname>
<Phone>415 986-7020</Phone>
<Notes>Other data</Notes>
</authors>
</dsPubs>

用名称 authors by hand.xml 保存此文件。注意,在这个新文件中,每个作者有一个Notes 元素,在 XML 中还有一个注释。

如果我们在运行应用程序后单击刚刚添加的按钮,数据集将被链接到新的XmlDataDocument 也将立即反映这些更改。同样,对XmlDataDocument 内的数据的任何更改将由数据集反映。

写入 XML 数据

一旦我们利用链接的数据集将数据加载到 XmlDataDocument,就可以通过这两个对象中的任何一个操作数据。操作数据后,我们很可能希望将它保存回 XML 文件。

如果我们使用数据集对象的WriteXml 方法来保存数据,我们会丢失数据集中没有显示的任何数据,以及文档中的任何注释。因此,我们可以将XmlDataDocument中的数据保存回文件,保留文档的所有数据、注释和格式设置。

通过下面的代码向名为 btnToXMLdoc 的 Form1 添加一个按钮:

Private Sub btnToXMLdoc_Click(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles btnToXMLdoc.Click
xmlDoc.Save("c:\authors by hand.xml")
End Sub

此代码设计为在我们单击 btnXMLdoc 后运行,将数据加载到XmlDataDocument 及其链接的数据集。

XmlDataDocument 的内容将保存回从中读取 XML 的文件。

如果我们运行应用程序、加载数据、编辑 DataGrid 中的字段,然后单击按钮,更改的数据将写回该文件。下面的 XML 是更改 DataGrid 中的作者的姓名、然后再更新 XML 文件的结果:

<?xml version="1.0" standalone="yes"?>
<dsPubs xmlns="http://www.tempuri.org/dsPubs.xsd">
<!-- a comment in the document -->
<authors>
<au_id>172-32-1176</au_id>
<au_lname>White</au_lname>
<au_fname>Gerold</au_fname>
<Phone>408 496-7223</Phone>
<Notes>Other data</Notes>
</a

如今有许多查看和使用数据的方法。传统上,我们使用诸如 ADO.NET 这样的数据访问技术,这种技术为我们提供了主要针对关系数据的强大功能。利用 ADO 和 ADO.NET,可以实现与非关系数据的交互,但真正的焦点是传统的表格式数据视图。最近,我们看到了 XML 的崛起,这种技术的核心是分层数据表示形式。

多年以前,在我接触到关系数据库之前,我使用的是层次数据库。在许多方面,XML 可以说是层次数据库概念的开放表示形式,但因为 XML 是基于文本的,能够利用 XSL 应用转换,再加上其他与 XML 相关的标准,它在可扩展性和表现能力方面远远超出层次数据库。

利用 ADO.NET,Microsoft 不仅提供对关系数据源中的传统表格式数据视图的支持,而且还集成了对 XML 的强大支持。ADO.NET 和 XML 之间的关系以若干个级别存在于基础结构中,因此我们可以基于自己的应用程序要求以灵活的方式使用这两种数据技术。

在本文中,我将讨论 ADO.NET 与 XML 配合使用的基本知识。

*

ADO.NET 和 XML 的结构

在编写代码之前,我希望快速回顾一下 ADO.NET 与 XML 集成的方式。然后,我们通过构建一个说明这两种技术如何配合使用的测试应用程序,在实践中应用概念。

ADO.NET 本身可分为两个部分。一部分是托管提供程序,负责处理与数据源的通讯。另一部分是数据集,为表格式数据提供内存中的存储区。数据集可以加载来自托管提供程序的数据,或者可以直接从我们的代码加载。另外,数据集也可以通过使用 XmlReader 加载数据。上述内容如图 1 所示。

向数据集加载数据的三种方式。

1. 向数据集加载数据的三种方式。

无论使用哪种方式,最终结果都是包含数据表的数据集,而数据表又包含我们已经加载的数据。数据表是表格式结构,因此数据被表示为一组行和列。

利用数据集,我们可以通过代码操纵数据,或者,我们可以使用数据绑定将数据连接到用户界面 (UI) 中的控件。一旦数据填充到数据集,无论数据来自 Microsoft SQL Server? 还是 XML 文档,在行为上都没有什么不同。这一特点可以实现很强大的功能,因为它意味着,我们可以从表加载数据,然后将数据保存到 XML 文件,我们也可以从 XML 文件加载数据,然后将数据存储到数据库中的表。

使用 XmlReader 方法用 XML 填充数据集时,它将自动为 XML 数据生成一个定义,即 XSD。XSD 是在 XML 文档被读取时从它的结构派生出来的。这一点非常有用,因为它带来了另外一个好处,即您可以使用数据集迅速生成从现有的 XML 文档派生的 XSD。我很喜欢这个随带的好处,因为手动编写 XSD 是我最不喜欢做的事情之一。

另一方面,如果您希望控制从 XML 文件读到数据集中的字段,您可以创建自己的 XSD,在读取 XML 数据之前用它来初始化数据集。然后,在读取 XML 数据时,只有与 XSD 字段相匹配的数据被读入数据集。XML 文档中的任何其他数据都将被忽略。

需要说明的是 — 如果您读取数据时使用自定义的 XSD,数据集将只包含由该 XSD 定义的数据。如果您后来又将数据集另存为 XML,则只保留该 XSD 定义的数据。原来的 XML 文件中的任何其他数据都不会包括在输出中。

还有一点需要注意,当直接从数据集使用 XML 时,无论有没有预定义的 XSD,数据集不保留 XML 文档的格式设置。XML 文档内的任何空格、注释或其他非数据元素,都将在数据集写出 XML 时丢失。

为了解决数据丢失、格式设置和注释丢失这些问题,ADO.NET 提供了另一种使用 XML 的机制。

如果您以前曾经在 .NET 中使用过 XML,您一定很熟悉 XmlDocument 对象。XmlDocument 读取 XML 文档,允许我们对它进行操作,然后可以将它保存回去。该对象可以保留格式设置、注释和所有数据。

Microsoft .NET 还包括 XmlDataDocument 对象。XmlDataDocument 具有 XmlDocument 的所有功能,此外,它能理解如何与 ADO.NET 数据集集成。如图 2 所示。

XmlDataDocument 与 ADO.NET 数据集集成。

2. XmlDataDocument 与 ADO.NET 数据集集成。

这意味着我们可以同时使用数据集以表格格式和通过 XmlDataDocument 以分层格式查看数据。由于 XSD 定义了可以使用数据集的特定数据,因此我们可以准确地控制 XmlDataDocument 中的哪些数据可以通过数据集提供。

通过数据集视图,我们可以利用数据绑定的功能,使熟悉使用表格式数据的广大开发人员可以访问 XML 数据。XmlDataDocument 视图允许我们操作所有 XML 数据,包括数据集中不提供的任何数据。另外,XmlDataDocument 继续保留空格、格式设置和注释,因此当我们将文档保存回去的时候,我们将得到与读取时相比,属于同一类别的文件。

XmlDataDocument 和数据集之间的集成是双向的。一旦它们链接在一起,对数据集中的数据的任何更新将立即反映在 XmlDataDocument 中。同样,对 XmlDataDocument 中的数据的任何更改也将立即反映在数据集中。

除了能够直接读取 XML、将 XML 写到数据集中,以及能够将XmlDataDocument与数据集链接在一起,ADO.NET 还提供了强大的 XML 支持。了解上述背景后,我们可以研究并编写一些代码来说明 ADO.NET 如何与 XML 配合使用。

我们将对基于专栏文章ADO.NET and You中的应用程序进行构建。您可以从 MSDN 下载该代码。

该应用程序已经有一个 DataGrid、一个到 Pubs 数据库的连接,并且能够检索和更新 Authors 表中的数据。该应用程序还有一些按钮,您可以用它们将表中的数据写到文件,并将该文件读回数据库表。此示例中的文件不是 XML,而是简单文本文件。我们将对该应用程序进行扩展,使之使用 XML 文件。

直接在数据集中使用 XML

DataSet 对象包括直接读写 XML 文件的方法。这些方法的一个优点是,我们可以从数据库加载数据,然后将数据写到 XML 文件。现在我们来增强应用程序,以便利用这些方法。

将 XML 保存到文件

将一个按钮添加到名为 btnToXML 的 Form1。我们将编写此按钮后面的代码,将当前在 dsPubs 数据集中的任何数据保存为 XML 文件:

Private Sub btnToXML_Click(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles btnToXML.Click
DsPubs.WriteXml("c:\authors.xml")
End Sub

XML 将基于数据表中的列名自动生成,并且将包括数据集中的所有数据。如果数据集有一个以上的DataTable 对象,数据将包括在输出中。假设我们已经将 Authors 表中的数据加载到数据集,单击此按钮即可生成其内容如下所示的文件:

<?xml version="1.0" standalone="yes"?>
<dsPubs xmlns="http://www.tempuri.org/dsPubs.xsd">
<authors>
<au_id>172-32-1176</au_id>
<au_lname>White</au_lname>
<au_fname>Gerry</au_fname>
<phone>408 496-7223</phone>
</authors>
<authors>
<au_id>213-46-8915</au_id>
<au_lname>Green</au_lname>
<au_fname>Marjorie</au_fname>
<phone>415 986-7020</phone>
</authors>
</dsPubs>

数据集中的所有数据都会包括在内。从该 XML 可以大致看出输出的模式。XML 简单而直接,没有特定于数据集的信息或 XSD。如果我们需要数据的 XSD,可以通过下面的更改指定 XSD:

Private Sub btnToXML_Click(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles btnToXML.Click
DsPubs.WriteXml("c:\authors.xml")
DsPubs.WriteXml("c:\authors with schema.xml", _
XmlWriteMode.WriteSchema
End Sub

现在我们创建了两个文件。第一个文件包含简单的 XML 格式的数据,第二个文件包含以相同格式呈现的相同数据,但前面带有定义了数据结构的 XSD 信息。

通常,数据集自动生成 XSD。另一方面,我们也可以定义自己的 XSD,并在加载数据之前将它加载到数据集。在这种情况下,此 XSD 将反映我们预先确定的定义。稍后将研究加载自己的 XSD。

在我们的示例中,dsPubs 的 XSD 是我们在创建DataSet 对象时由 DataAdapter 向导自动生成的。以下是 XSD 的具体内容(部分文本已换行):

<?xml version="1.0" standalone="yes"?>
<dsPubs xmlns="http://www.tempuri.org/dsPubs.xsd">
<xs:schema id="dsPubs"
targetNamespace="http://www.tempuri.org/dsPubs.xsd"
xmlns:mstns="http://www.tempuri.org/dsPubs.xsd"
xmlns="http://www.tempuri.org/dsPubs.xsd"
xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-
microsoft-com:xml-msdata" attributeFormDefault="qualified"
elementFormDefault="qualified">
<xs:element name="dsPubs" msdata:IsDataSet="true">
<xs:complexType>
<xs:choice maxOccurs="unbounded">
<xs:element name="authors">
<xs:complexType>
<xs:sequence>
<xs:element name="au_id" type="xs:string" />
<xs:element name="au_lname" type="xs:string" />
<xs:element name="au_fname" type="xs:string" />
<xs:element name="phone" type="xs:string" />
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:choice>
</xs:complexType>
<xs:unique name="Constraint1" msdata:PrimaryKey="true">
<xs:selector xpath=".//mstns:authors" />
<xs:field xpath="mstns:au_id" />
</xs:unique>
</xs:element>
</xs:schema>

Author 数据紧跟在 XSD 的后面。注意,XSD 知道唯一主键值。这是因为 DataAdapter 向导是基于数据库定义生成 XSD 的。而从原始数据派生的 XSD 则不会包括这样的高级概念。

例如,如果您从 authors.xml 将 XML 加载到一般的 DataSet 对象,然后通过如下代码写出 XML(不输入此代码):

Dim dsXML As New DataSet()
dsXML.ReadXml("c:\authors.xml")
dsXML.WriteXml("c:\authors with schema.xml", _
XmlWriteMode.WriteSchema)

得到的 XSD 会更简单(部分文本已换行):

<?xml version="1.0" standalone="yes"?>
<dsPubs xmlns="http://www.tempuri.org/dsPubs.xsd">
<xs:schema id="dsPubs"
targetNamespace="http://www.tempuri.org/dsPubs.xsd"
xmlns:mstns="http://www.tempuri.org/dsPubs.xsd"
xmlns="http://www.tempuri.org/dsPubs.xsd"
xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-
microsoft-com:xml-msdata" attributeFormDefault="qualified"
elementFormDefault="qualified">
<xs:element name="dsPubs" msdata:IsDataSet="true">
<xs:complexType>
<xs:choice maxOccurs="unbounded">
<xs:element name="authors">
<xs:complexType>
<xs:sequence>
<xs:element name="au_id" type="xs:string" minOccurs="0" />
<xs:element name="au_lname" type="xs:string"
minOccurs="0" />
<xs:element name="au_fname" type="xs:string"
minOccurs="0" />
<xs:element name="phone" type="xs:string" minOccurs="0" />
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:choice>
</xs:complexType>
</xs:element>
</xs:schema>

这里再次提醒大家,Author 数据紧跟在 XSD 的后面。

注意,前面没有提到主键。这类信息是无法从简单的 XML 数据派生的。这种 XSD 只包括可以直接从数据本身推断出的基本信息。 我们可以增强代码的功能,不仅能够编写出 XML 及具有架构的 XML 的代码,还可以编写出架构本身的代码:

Private Sub btnToXML_Click(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles btnToXML.Click
DsPubs.WriteXml("c:\authors.xml")
DsPubs.WriteXml("c:\authors with schema.xml", _
XmlWriteMode.WriteSchema)
DsPubs.WriteXmlSchema("c:\authors.xsd")
End Sub

还要记住的是,由数据集编写出的 XML 和 XSD 都不包含注释或特殊格式设置,也不包含数据集中包含的数据之外的任何数据。

从文件读取 XML

将 XML 文件读到数据集的过程也同样简单。通过在窗体背后使用下面的代码,可将另一个按钮添加到名为 btnFromXML 的 Form1(窗体 1):

Private Sub btnFromXML_Click(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles btnFromXML.Click
DsPubs.Clear()
DsPubs.ReadXml("c:\authors.xml")
End Sub

ReadXml 方法从文件读取数据,然后将数据加载到数据集。

Clear 方法清除 dsPubs 中已有的任何数据,这一点很重要,因为将 XML 读到数据集与添加一组新行是一样的。由于我们使用的是利用 DataAdapter 向导生成的类型化数据集(如 dsPubs),数据集将强制执行诸如必须具有唯一主键这样的规则。因此,如果我们尝试从 XML 文件加载的数据与数据集中已有的数据相同,将导致运行时错误。

如果我们的 DataSet 对象是一般数据集,其表现将有所不同。例如,我们可以编写下面的代码(不输入此代码):

Dim dsXML As New DataSet()
dsXML.ReadXml("c:\authors.xml")
dsXML.ReadXml("c:\authors.xml")

其结果是两次包含所有数据的数据集。该数据集将自动生成数据的 XSD,但生成的 XSD 不知道唯一主键值。这样就可以多次加载相同的数据行。

使用正确的 XSD

我们已经看到数据集使用的 XML 定义(或 XSD)在行为上有很大的不同。我们的控制已超出数据集是否知道唯一主键这个问题。通过预定义数据集的 XSD,我们可以确切地控制哪些数据字段从源 XML 文件读取,从而确定哪些字段将在我们保存 XML 时被写出。

我们一直在使用的数据集 dsPubs 是使用 "ADO.NET and You" 一文中的 DataAdapter 向导生成的。这意味着它是一个类型化数据集,带有预定义的 XSD。我们可以在 Visual Studio? .NET 中使用其内置的 XSD 设计器编辑此 XSD。dsPubs.xsd 是解决方案资源管理器中的一个文件。通过双击打开设计器,如图 3 所示。

Visual Studio .NET 中的 XSD 设计器。

3. Visual Studio .NET 中的 XSD 设计器。

设计器在au_id 字段的旁边显示了一个钥匙图标,说明它就是唯一主键。设计器还列出可加载到数据集中的所有数据的字段名和数据类型。您可以使用此设计器来添加其他字段、删除字段或更改字段的数据类型。

有一点需要注意的是,当我们从SqlDataAdapter将数据加载到数据集时,这里显示的 XSD 将被忽略。举例来说,即使我们通过编辑它来删除Phone字段,当我们重新从数据库加载数据时,Phone 字段将还原,因为 XSD 将基于 SQL 定义被重新创建。

不过,当我们从 XML 文件将数据加载到数据集时,将忽略这里显示的 XSD。要了解具体过程,请在Phone 字段的左侧右击,然后从弹出式菜单中选择Delete。此操作将从 XSD 中删除Phone字段。

现在运行应用程序,然后单击From XML 按钮将 authors.xml 文件加载到数据集。注意,Phone字段没有显示在网格中,也没有加载到数据集中。该字段不匹配 XSD,因此被忽略了。

现在再回到 btnToXML 的代码,其中有一行代码将 XSD 从数据集写到一个名为 authors.xsd 的文件。Visual Studio .NET 也使用 XSD 设计器来编辑此文件。选择File | Open | File,然后选择 authors.xsd。该文件将在同样类型的设计器中打开,我们可以用图形方式编辑 XSD,或通过单击设计器底部的XML选项卡直接编辑。

为进行此演示,通过在该字段左侧右击后选择Delete,删除Phone 字段。您可能必须先用左键单击Phone 字段,来选择该字段,否则,Delete 操作将从 XSD 删除整个 Authors 实体。之后,保存该文件。

我们可以创建一个一般的数据集,并从诸如 authors.xsd 这样的文件将 XSD 加载到该数据集中。向名为 btnXSD 的 Form1 添加一个新按钮,并为一般数据集添加以下窗体级变量声明:

Private dsXML As DataSet

然后在新按钮后面加入以下代码:

Private Sub btnXSD_Click(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles btnXSD.Click
' Create a generic DataSet
dsXML = New DataSet()
' Load the XSD into the DataSet
dsXML.ReadXmlSchema("c:\authors.xsd")
' Load the XML data into the DataSet
dsXML.ReadXml("c:\authors.xml")
' Bind the grid to this new DataSet
DataGrid1.DataSource = dsXML.Tables(0)
End Sub

我们做的第一件事就是创建一个一般的DataSet对象。与具有相关联的 XSD 的类型化数据集 dsPubs 不同,这个新的数据集完全未定义,并且是空的,没有任何 XSD。

然后,我们将 XSD 文件 authors.xsd 加载到该数据集。这使得数据集拥有了数据的定义。在这种情况下,该定义知道主键字段,但由于我们删除了 Phone 字段,它并不知道该数据。

然后我们从 authors.xml 加载了 XML 数据本身。该文件的确包含 Phone 信息,但该信息被忽略了,因为它不匹配 XSD。接下来,DataGrid 被绑定到这个新的DataSet 对象,因此将显示结果。

使用自定义的 XSD 来加载数据时的数据显示。

4. 使用自定义的 XSD 来加载数据时的数据显示。

图 4 说明了为什么知道我们使用的是自动派生的 XSD(即 Visual Studio .NET 中的 DataAdapter 向导创建的 XSD),还是手动加载的 XSD 非常重要而且很有用。通过控制架构,我们可以控制加载的数据,以及数据在加载后的行为。

使用 XmlDataDocument 和数据集

到目前为止我们一直直接从数据集使用 XML。虽然这样做非常方便,而且功能相当强大,但也存在缺点。当我们混合使用 XML 数据源和非 XML 数据源时,那些技巧最有效。但我们如果读取和更新现有的 XML 文件,就会遇到限制。

尤其是,数据集只加载 XSD 架构为数据集定义的 XML 数据,忽略 XML 源中的所有其他数据。从数据集将数据写入 XML 时,只写入数据集中的数据,原来的 XML 文件中的任何其他数据将丢失。可能更糟的是,将数据读入数据集,然后再从数据集保存到 XML 文件时,原来的 XML 文件中的任何注释或特殊格式设置都会丢失。

为了克服这些限制,我们可以将XmlDataDocument 与数据集一起使用。XML 数据将加载到XmlDataDocument 对象,该对象将链接到一个数据集。我们可以直接通过XmlDataDocument 使用 XML 数据,或者,可以使用数据集以表格视图使用 XML 数据。

读取 XML 数据

在 Form1 中,为 Xml 命名空间添加Imports 语句:

Imports System.Xml

然后添加XmlDataDocument的窗体级变量声明:

Private xmlDoc As XmlDataDocument

现在,向名为 btnXMLdoc 的 Form1 添加一个按钮,该按钮将触发通过相关联的数据集将一个 XML 文档加载到XmlDataDocument 的过程:

Private Sub btnXMLdoc_Click(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles btnXMLdoc.Click
DsPubs.Clear()
xmlDoc = New XmlDataDocument(DsPubs)
xmlDoc.Load("c:\authors by hand.xml")
End Sub

首先我们要清除数据集,以确保它不包含任何数据。然后将DataSet 对象链接到一个新的XmlDataDocument 对象。此时,我们加载到XmlDataDocument的任何数据都将反映在数据集中。也就是说,是与定义数据集的 XSD 相匹配的任何数据。由于我们使用的是 dsPubs,我们知道它有一个相关联的 XSD,该 XSD 涵盖了我们一直在使用的 Author 数据。

为了了解具体过程,我们需要一个包括某些额外数据和一个注释的 XML 文档,这样就可以知道这类信息是如何保留的。 使用 Visual Studio .NET 打开 authors.xml 文件,然后编辑该文件,如下所示:

<?xml version="1.0" standalone="yes"?>
<dsPubs xmlns="http://www.tempuri.org/dsPubs.xsd">
<!-- a comment in the document -->
<authors>
<au_id>172-32-1176</au_id>
<au_lname>White</au_lname>
<au_fname>Gerry</au_fname>
<Phone>408 496-7223</Phone>
<Notes>Other data</Notes>
</authors>
<authors>
<au_id>213-46-8915</au_id>
<au_lname>Green</au_lname>
<au_fname>Marjorie</au_fname>
<Phone>415 986-7020</Phone>
<Notes>Other data</Notes>
</authors>
</dsPubs>

用名称 authors by hand.xml 保存此文件。注意,在这个新文件中,每个作者有一个Notes 元素,在 XML 中还有一个注释。

如果我们在运行应用程序后单击刚刚添加的按钮,数据集将被链接到新的XmlDataDocument 也将立即反映这些更改。同样,对XmlDataDocument 内的数据的任何更改将由数据集反映。

写入 XML 数据

一旦我们利用链接的数据集将数据加载到 XmlDataDocument,就可以通过这两个对象中的任何一个操作数据。操作数据后,我们很可能希望将它保存回 XML 文件。

如果我们使用数据集对象的WriteXml 方法来保存数据,我们会丢失数据集中没有显示的任何数据,以及文档中的任何注释。因此,我们可以将XmlDataDocument中的数据保存回文件,保留文档的所有数据、注释和格式设置。

通过下面的代码向名为 btnToXMLdoc 的 Form1 添加一个按钮:

Private Sub btnToXMLdoc_Click(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles btnToXMLdoc.Click
xmlDoc.Save("c:\authors by hand.xml")
End Sub

此代码设计为在我们单击 btnXMLdoc 后运行,将数据加载到XmlDataDocument 及其链接的数据集。

XmlDataDocument 的内容将保存回从中读取 XML 的文件。

如果我们运行应用程序、加载数据、编辑 DataGrid 中的字段,然后单击按钮,更改的数据将写回该文件。下面的 XML 是更改 DataGrid 中的作者的姓名、然后再更新 XML 文件的结果:

<?xml version="1.0" standalone="yes"?>
<dsPubs xmlns="http://www.tempuri.org/dsPubs.xsd">
<!-- a comment in the document -->
<authors>
<au_id>172-32-1176</au_id>
<au_lname>White</au_lname>
<au_fname>Gerold</au_fname>
<Phone>408 496-7223</Phone>
<Notes>Other data</Notes>
</authors>
<authors>
<au_id>213-46-8915</au_id>
<au_lname>Green-Black</au_lname>
<au_fname>Marjorie</au_fname>
<Phone>415 986-7020</Phone>
<Notes>Other data</Notes>
</authors>
</dsPubs>

注意,注释和 <Notes> 元素保留下来,数据已被更新。上面的示例说明了应该怎样同时直接利用 XmlDocument 和 ADO.NET 数据集方法的功能来使用数据。

小结

在本文中,我讨论了在 ADO.NET 中使用 XML 的基本知识。过去,传统的数据访问技术和 XML 之间一直有着藕断丝连的关系。利用 ADO.NET 和 XmlDataDocument,我们很好地将这两者结合起来。我们可以执行直接从数据集读写 XML 的基本操作,当 XML 变得更加复杂时,我们可以通过一个链接的数据集将它加载到XmlDataDocument,这样就可以同时获得数据的 XML 和表格视图。

如今有许多查看和使用数据的方法。传统上,我们使用诸如 ADO.NET 这样的数据访问技术,这种技术为我们提供了主要针对关系数据的强大功能。利用 ADO 和 ADO.NET,可以实现与非关系数据的交互,但真正的焦点是传统的表格式数据视图。最近,我们看到了 XML 的崛起,这种技术的核心是分层数据表示形式。

多年以前,在我接触到关系数据库之前,我使用的是层次数据库。在许多方面,XML 可以说是层次数据库概念的开放表示形式,但因为 XML 是基于文本的,能够利用 XSL 应用转换,再加上其他与 XML 相关的标准,它在可扩展性和表现能力方面远远超出层次数据库。

利用 ADO.NET,Microsoft 不仅提供对关系数据源中的传统表格式数据视图的支持,而且还集成了对 XML 的强大支持。ADO.NET 和 XML 之间的关系以若干个级别存在于基础结构中,因此我们可以基于自己的应用程序要求以灵活的方式使用这两种数据技术。

在本文中,我将讨论 ADO.NET 与 XML 配合使用的基本知识。

*

ADO.NET 和 XML 的结构

在编写代码之前,我希望快速回顾一下 ADO.NET 与 XML 集成的方式。然后,我们通过构建一个说明这两种技术如何配合使用的测试应用程序,在实践中应用概念。

ADO.NET 本身可分为两个部分。一部分是托管提供程序,负责处理与数据源的通讯。另一部分是数据集,为表格式数据提供内存中的存储区。数据集可以加载来自托管提供程序的数据,或者可以直接从我们的代码加载。另外,数据集也可以通过使用 XmlReader 加载数据。上述内容如图 1 所示。

向数据集加载数据的三种方式。

1. 向数据集加载数据的三种方式。

无论使用哪种方式,最终结果都是包含数据表的数据集,而数据表又包含我们已经加载的数据。数据表是表格式结构,因此数据被表示为一组行和列。

利用数据集,我们可以通过代码操纵数据,或者,我们可以使用数据绑定将数据连接到用户界面 (UI) 中的控件。一旦数据填充到数据集,无论数据来自 Microsoft SQL Server? 还是 XML 文档,在行为上都没有什么不同。这一特点可以实现很强大的功能,因为它意味着,我们可以从表加载数据,然后将数据保存到 XML 文件,我们也可以从 XML 文件加载数据,然后将数据存储到数据库中的表。

使用 XmlReader 方法用 XML 填充数据集时,它将自动为 XML 数据生成一个定义,即 XSD。XSD 是在 XML 文档被读取时从它的结构派生出来的。这一点非常有用,因为它带来了另外一个好处,即您可以使用数据集迅速生成从现有的 XML 文档派生的 XSD。我很喜欢这个随带的好处,因为手动编写 XSD 是我最不喜欢做的事情之一。

另一方面,如果您希望控制从 XML 文件读到数据集中的字段,您可以创建自己的 XSD,在读取 XML 数据之前用它来初始化数据集。然后,在读取 XML 数据时,只有与 XSD 字段相匹配的数据被读入数据集。XML 文档中的任何其他数据都将被忽略。

需要说明的是 — 如果您读取数据时使用自定义的 XSD,数据集将只包含由该 XSD 定义的数据。如果您后来又将数据集另存为 XML,则只保留该 XSD 定义的数据。原来的 XML 文件中的任何其他数据都不会包括在输出中。

还有一点需要注意,当直接从数据集使用 XML 时,无论有没有预定义的 XSD,数据集不保留 XML 文档的格式设置。XML 文档内的任何空格、注释或其他非数据元素,都将在数据集写出 XML 时丢失。

为了解决数据丢失、格式设置和注释丢失这些问题,ADO.NET 提供了另一种使用 XML 的机制。

如果您以前曾经在 .NET 中使用过 XML,您一定很熟悉 XmlDocument 对象。XmlDocument 读取 XML 文档,允许我们对它进行操作,然后可以将它保存回去。该对象可以保留格式设置、注释和所有数据。

Microsoft .NET 还包括 XmlDataDocument 对象。XmlDataDocument 具有 XmlDocument 的所有功能,此外,它能理解如何与 ADO.NET 数据集集成。如图 2 所示。

XmlDataDocument 与 ADO.NET 数据集集成。

2. XmlDataDocument 与 ADO.NET 数据集集成。

这意味着我们可以同时使用数据集以表格格式和通过 XmlDataDocument 以分层格式查看数据。由于 XSD 定义了可以使用数据集的特定数据,因此我们可以准确地控制 XmlDataDocument 中的哪些数据可以通过数据集提供。

通过数据集视图,我们可以利用数据绑定的功能,使熟悉使用表格式数据的广大开发人员可以访问 XML 数据。XmlDataDocument 视图允许我们操作所有 XML 数据,包括数据集中不提供的任何数据。另外,XmlDataDocument 继续保留空格、格式设置和注释,因此当我们将文档保存回去的时候,我们将得到与读取时相比,属于同一类别的文件。

XmlDataDocument 和数据集之间的集成是双向的。一旦它们链接在一起,对数据集中的数据的任何更新将立即反映在 XmlDataDocument 中。同样,对 XmlDataDocument 中的数据的任何更改也将立即反映在数据集中。

除了能够直接读取 XML、将 XML 写到数据集中,以及能够将XmlDataDocument与数据集链接在一起,ADO.NET 还提供了强大的 XML 支持。了解上述背景后,我们可以研究并编写一些代码来说明 ADO.NET 如何与 XML 配合使用。

我们将对基于专栏文章ADO.NET and You中的应用程序进行构建。您可以从 MSDN 下载该代码。

该应用程序已经有一个 DataGrid、一个到 Pubs 数据库的连接,并且能够检索和更新 Authors 表中的数据。该应用程序还有一些按钮,您可以用它们将表中的数据写到文件,并将该文件读回数据库表。此示例中的文件不是 XML,而是简单文本文件。我们将对该应用程序进行扩展,使之使用 XML 文件。

直接在数据集中使用 XML

DataSet 对象包括直接读写 XML 文件的方法。这些方法的一个优点是,我们可以从数据库加载数据,然后将数据写到 XML 文件。现在我们来增强应用程序,以便利用这些方法。

将 XML 保存到文件

将一个按钮添加到名为 btnToXML 的 Form1。我们将编写此按钮后面的代码,将当前在 dsPubs 数据集中的任何数据保存为 XML 文件:

Private Sub btnToXML_Click(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles btnToXML.Click
DsPubs.WriteXml("c:\authors.xml")
End Sub

XML 将基于数据表中的列名自动生成,并且将包括数据集中的所有数据。如果数据集有一个以上的DataTable 对象,数据将包括在输出中。假设我们已经将 Authors 表中的数据加载到数据集,单击此按钮即可生成其内容如下所示的文件:

<?xml version="1.0" standalone="yes"?>
<dsPubs xmlns="http://www.tempuri.org/dsPubs.xsd">
<authors>
<au_id>172-32-1176</au_id>
<au_lname>White</au_lname>
<au_fname>Gerry</au_fname>
<phone>408 496-7223</phone>
</authors>
<authors>
<au_id>213-46-8915</au_id>
<au_lname>Green</au_lname>
<au_fname>Marjorie</au_fname>
<phone>415 986-7020</phone>
</authors>
</dsPubs>

数据集中的所有数据都会包括在内。从该 XML 可以大致看出输出的模式。XML 简单而直接,没有特定于数据集的信息或 XSD。如果我们需要数据的 XSD,可以通过下面的更改指定 XSD:

Private Sub btnToXML_Click(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles btnToXML.Click
DsPubs.WriteXml("c:\authors.xml")
DsPubs.WriteXml("c:\authors with schema.xml", _
XmlWriteMode.WriteSchema
End Sub

现在我们创建了两个文件。第一个文件包含简单的 XML 格式的数据,第二个文件包含以相同格式呈现的相同数据,但前面带有定义了数据结构的 XSD 信息。

通常,数据集自动生成 XSD。另一方面,我们也可以定义自己的 XSD,并在加载数据之前将它加载到数据集。在这种情况下,此 XSD 将反映我们预先确定的定义。稍后将研究加载自己的 XSD。

在我们的示例中,dsPubs 的 XSD 是我们在创建DataSet 对象时由 DataAdapter 向导自动生成的。以下是 XSD 的具体内容(部分文本已换行):

<?xml version="1.0" standalone="yes"?>
<dsPubs xmlns="http://www.tempuri.org/dsPubs.xsd">
<xs:schema id="dsPubs"
targetNamespace="http://www.tempuri.org/dsPubs.xsd"
xmlns:mstns="http://www.tempuri.org/dsPubs.xsd"
xmlns="http://www.tempuri.org/dsPubs.xsd"
xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-
microsoft-com:xml-msdata" attributeFormDefault="qualified"
elementFormDefault="qualified">
<xs:element name="dsPubs" msdata:IsDataSet="true">
<xs:complexType>
<xs:choice maxOccurs="unbounded">
<xs:element name="authors">
<xs:complexType>
<xs:sequence>
<xs:element name="au_id" type="xs:string" />
<xs:element name="au_lname" type="xs:string" />
<xs:element name="au_fname" type="xs:string" />
<xs:element name="phone" type="xs:string" />
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:choice>
</xs:complexType>
<xs:unique name="Constraint1" msdata:PrimaryKey="true">
<xs:selector xpath=".//mstns:authors" />
<xs:field xpath="mstns:au_id" />
</xs:unique>
</xs:element>
</xs:schema>

Author 数据紧跟在 XSD 的后面。注意,XSD 知道唯一主键值。这是因为 DataAdapter 向导是基于数据库定义生成 XSD 的。而从原始数据派生的 XSD 则不会包括这样的高级概念。

例如,如果您从 authors.xml 将 XML 加载到一般的 DataSet 对象,然后通过如下代码写出 XML(不输入此代码):

Dim dsXML As New DataSet()
dsXML.ReadXml("c:\authors.xml")
dsXML.WriteXml("c:\authors with schema.xml", _
XmlWriteMode.WriteSchema)

得到的 XSD 会更简单(部分文本已换行):

<?xml version="1.0" standalone="yes"?>
<dsPubs xmlns="http://www.tempuri.org/dsPubs.xsd">
<xs:schema id="dsPubs"
targetNamespace="http://www.tempuri.org/dsPubs.xsd"
xmlns:mstns="http://www.tempuri.org/dsPubs.xsd"
xmlns="http://www.tempuri.org/dsPubs.xsd"
xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-
microsoft-com:xml-msdata" attributeFormDefault="qualified"
elementFormDefault="qualified">
<xs:element name="dsPubs" msdata:IsDataSet="true">
<xs:complexType>
<xs:choice maxOccurs="unbounded">
<xs:element name="authors">
<xs:complexType>
<xs:sequence>
<xs:element name="au_id" type="xs:string" minOccurs="0" />
<xs:element name="au_lname" type="xs:string"
minOccurs="0" />
<xs:element name="au_fname" type="xs:string"
minOccurs="0" />
<xs:element name="phone" type="xs:string" minOccurs="0" />
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:choice>
</xs:complexType>
</xs:element>
</xs:schema>

这里再次提醒大家,Author 数据紧跟在 XSD 的后面。

注意,前面没有提到主键。这类信息是无法从简单的 XML 数据派生的。这种 XSD 只包括可以直接从数据本身推断出的基本信息。 我们可以增强代码的功能,不仅能够编写出 XML 及具有架构的 XML 的代码,还可以编写出架构本身的代码:

Private Sub btnToXML_Click(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles btnToXML.Click
DsPubs.WriteXml("c:\authors.xml")
DsPubs.WriteXml("c:\authors with schema.xml", _
XmlWriteMode.WriteSchema)
DsPubs.WriteXmlSchema("c:\authors.xsd")
End Sub

还要记住的是,由数据集编写出的 XML 和 XSD 都不包含注释或特殊格式设置,也不包含数据集中包含的数据之外的任何数据。

从文件读取 XML

将 XML 文件读到数据集的过程也同样简单。通过在窗体背后使用下面的代码,可将另一个按钮添加到名为 btnFromXML 的 Form1(窗体 1):

Private Sub btnFromXML_Click(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles btnFromXML.Click
DsPubs.Clear()
DsPubs.ReadXml("c:\authors.xml")
End Sub

ReadXml 方法从文件读取数据,然后将数据加载到数据集。

Clear 方法清除 dsPubs 中已有的任何数据,这一点很重要,因为将 XML 读到数据集与添加一组新行是一样的。由于我们使用的是利用 DataAdapter 向导生成的类型化数据集(如 dsPubs),数据集将强制执行诸如必须具有唯一主键这样的规则。因此,如果我们尝试从 XML 文件加载的数据与数据集中已有的数据相同,将导致运行时错误。

如果我们的 DataSet 对象是一般数据集,其表现将有所不同。例如,我们可以编写下面的代码(不输入此代码):

Dim dsXML As New DataSet()
dsXML.ReadXml("c:\authors.xml")
dsXML.ReadXml("c:\authors.xml")

其结果是两次包含所有数据的数据集。该数据集将自动生成数据的 XSD,但生成的 XSD 不知道唯一主键值。这样就可以多次加载相同的数据行。

使用正确的 XSD

我们已经看到数据集使用的 XML 定义(或 XSD)在行为上有很大的不同。我们的控制已超出数据集是否知道唯一主键这个问题。通过预定义数据集的 XSD,我们可以确切地控制哪些数据字段从源 XML 文件读取,从而确定哪些字段将在我们保存 XML 时被写出。

我们一直在使用的数据集 dsPubs 是使用 "ADO.NET and You" 一文中的 DataAdapter 向导生成的。这意味着它是一个类型化数据集,带有预定义的 XSD。我们可以在 Visual Studio? .NET 中使用其内置的 XSD 设计器编辑此 XSD。dsPubs.xsd 是解决方案资源管理器中的一个文件。通过双击打开设计器,如图 3 所示。

Visual Studio .NET 中的 XSD 设计器。

3. Visual Studio .NET 中的 XSD 设计器。

设计器在au_id 字段的旁边显示了一个钥匙图标,说明它就是唯一主键。设计器还列出可加载到数据集中的所有数据的字段名和数据类型。您可以使用此设计器来添加其他字段、删除字段或更改字段的数据类型。

有一点需要注意的是,当我们从SqlDataAdapter将数据加载到数据集时,这里显示的 XSD 将被忽略。举例来说,即使我们通过编辑它来删除Phone字段,当我们重新从数据库加载数据时,Phone 字段将还原,因为 XSD 将基于 SQL 定义被重新创建。

不过,当我们从 XML 文件将数据加载到数据集时,将忽略这里显示的 XSD。要了解具体过程,请在Phone 字段的左侧右击,然后从弹出式菜单中选择Delete。此操作将从 XSD 中删除Phone字段。

现在运行应用程序,然后单击From XML 按钮将 authors.xml 文件加载到数据集。注意,Phone字段没有显示在网格中,也没有加载到数据集中。该字段不匹配 XSD,因此被忽略了。

现在再回到 btnToXML 的代码,其中有一行代码将 XSD 从数据集写到一个名为 authors.xsd 的文件。Visual Studio .NET 也使用 XSD 设计器来编辑此文件。选择File | Open | File,然后选择 authors.xsd。该文件将在同样类型的设计器中打开,我们可以用图形方式编辑 XSD,或通过单击设计器底部的XML选项卡直接编辑。

为进行此演示,通过在该字段左侧右击后选择Delete,删除Phone 字段。您可能必须先用左键单击Phone 字段,来选择该字段,否则,Delete 操作将从 XSD 删除整个 Authors 实体。之后,保存该文件。

我们可以创建一个一般的数据集,并从诸如 authors.xsd 这样的文件将 XSD 加载到该数据集中。向名为 btnXSD 的 Form1 添加一个新按钮,并为一般数据集添加以下窗体级变量声明:

Private dsXML As DataSet

然后在新按钮后面加入以下代码:

Private Sub btnXSD_Click(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles btnXSD.Click
' Create a generic DataSet
dsXML = New DataSet()
' Load the XSD into the DataSet
dsXML.ReadXmlSchema("c:\authors.xsd")
' Load the XML data into the DataSet
dsXML.ReadXml("c:\authors.xml")
' Bind the grid to this new DataSet
DataGrid1.DataSource = dsXML.Tables(0)
End Sub

我们做的第一件事就是创建一个一般的DataSet对象。与具有相关联的 XSD 的类型化数据集 dsPubs 不同,这个新的数据集完全未定义,并且是空的,没有任何 XSD。

然后,我们将 XSD 文件 authors.xsd 加载到该数据集。这使得数据集拥有了数据的定义。在这种情况下,该定义知道主键字段,但由于我们删除了 Phone 字段,它并不知道该数据。

然后我们从 authors.xml 加载了 XML 数据本身。该文件的确包含 Phone 信息,但该信息被忽略了,因为它不匹配 XSD。接下来,DataGrid 被绑定到这个新的DataSet 对象,因此将显示结果。

使用自定义的 XSD 来加载数据时的数据显示。

4. 使用自定义的 XSD 来加载数据时的数据显示。

图 4 说明了为什么知道我们使用的是自动派生的 XSD(即 Visual Studio .NET 中的 DataAdapter 向导创建的 XSD),还是手动加载的 XSD 非常重要而且很有用。通过控制架构,我们可以控制加载的数据,以及数据在加载后的行为。

使用 XmlDataDocument 和数据集

到目前为止我们一直直接从数据集使用 XML。虽然这样做非常方便,而且功能相当强大,但也存在缺点。当我们混合使用 XML 数据源和非 XML 数据源时,那些技巧最有效。但我们如果读取和更新现有的 XML 文件,就会遇到限制。

尤其是,数据集只加载 XSD 架构为数据集定义的 XML 数据,忽略 XML 源中的所有其他数据。从数据集将数据写入 XML 时,只写入数据集中的数据,原来的 XML 文件中的任何其他数据将丢失。可能更糟的是,将数据读入数据集,然后再从数据集保存到 XML 文件时,原来的 XML 文件中的任何注释或特殊格式设置都会丢失。

为了克服这些限制,我们可以将XmlDataDocument 与数据集一起使用。XML 数据将加载到XmlDataDocument 对象,该对象将链接到一个数据集。我们可以直接通过XmlDataDocument 使用 XML 数据,或者,可以使用数据集以表格视图使用 XML 数据。

读取 XML 数据

在 Form1 中,为 Xml 命名空间添加Imports 语句:

Imports System.Xml

然后添加XmlDataDocument的窗体级变量声明:

Private xmlDoc As XmlDataDocument

现在,向名为 btnXMLdoc 的 Form1 添加一个按钮,该按钮将触发通过相关联的数据集将一个 XML 文档加载到XmlDataDocument 的过程:

Private Sub btnXMLdoc_Click(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles btnXMLdoc.Click
DsPubs.Clear()
xmlDoc = New XmlDataDocument(DsPubs)
xmlDoc.Load("c:\authors by hand.xml")
End Sub

首先我们要清除数据集,以确保它不包含任何数据。然后将DataSet 对象链接到一个新的XmlDataDocument 对象。此时,我们加载到XmlDataDocument的任何数据都将反映在数据集中。也就是说,是与定义数据集的 XSD 相匹配的任何数据。由于我们使用的是 dsPubs,我们知道它有一个相关联的 XSD,该 XSD 涵盖了我们一直在使用的 Author 数据。

为了了解具体过程,我们需要一个包括某些额外数据和一个注释的 XML 文档,这样就可以知道这类信息是如何保留的。 使用 Visual Studio .NET 打开 authors.xml 文件,然后编辑该文件,如下所示:

<?xml version="1.0" standalone="yes"?>
<dsPubs xmlns="http://www.tempuri.org/dsPubs.xsd">
<!-- a comment in the document -->
<authors>
<au_id>172-32-1176</au_id>
<au_lname>White</au_lname>
<au_fname>Gerry</au_fname>
<Phone>408 496-7223</Phone>
<Notes>Other data</Notes>
</authors>
<authors>
<au_id>213-46-8915</au_id>
<au_lname>Green</au_lname>
<au_fname>Marjorie</au_fname>
<Phone>415 986-7020</Phone>
<Notes>Other data</Notes>
</authors>
</dsPubs>

用名称 authors by hand.xml 保存此文件。注意,在这个新文件中,每个作者有一个Notes 元素,在 XML 中还有一个注释。

如果我们在运行应用程序后单击刚刚添加的按钮,数据集将被链接到新的XmlDataDocument 也将立即反映这些更改。同样,对XmlDataDocument 内的数据的任何更改将由数据集反映。

写入 XML 数据

一旦我们利用链接的数据集将数据加载到 XmlDataDocument,就可以通过这两个对象中的任何一个操作数据。操作数据后,我们很可能希望将它保存回 XML 文件。

如果我们使用数据集对象的WriteXml 方法来保存数据,我们会丢失数据集中没有显示的任何数据,以及文档中的任何注释。因此,我们可以将XmlDataDocument中的数据保存回文件,保留文档的所有数据、注释和格式设置。

通过下面的代码向名为 btnToXMLdoc 的 Form1 添加一个按钮:

Private Sub btnToXMLdoc_Click(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles btnToXMLdoc.Click
xmlDoc.Save("c:\authors by hand.xml")
End Sub

此代码设计为在我们单击 btnXMLdoc 后运行,将数据加载到XmlDataDocument 及其链接的数据集。

XmlDataDocument 的内容将保存回从中读取 XML 的文件。

如果我们运行应用程序、加载数据、编辑 DataGrid 中的字段,然后单击按钮,更改的数据将写回该文件。下面的 XML 是更改 DataGrid 中的作者的姓名、然后再更新 XML 文件的结果:

<?xml version="1.0" standalone="yes"?>
<dsPubs xmlns="http://www.tempuri.org/dsPubs.xsd">
<!-- a comment in the document -->
<authors>
<au_id>172-32-1176</au_id>
<au_lname>White</au_lname>
<au_fname>Gerold</au_fname>
<Phone>408 496-7223</Phone>
<Notes>Other data</Notes>
</authors>
<authors>
<au_id>213-46-8915</au_id>
<au_lname>Green-Black</au_lname>
<au_fname>Marjorie</au_fname>
<Phone>415 986-7020</Phone>
<Notes>Other data</Notes>
</authors>
</dsPubs>

注意,注释和 <Notes> 元素保留下来,数据已被更新。上面的示例说明了应该怎样同时直接利用 XmlDocument 和 ADO.NET 数据集方法的功能来使用数据。

小结

在本文中,我讨论了在 ADO.NET 中使用 XML 的基本知识。过去,传统的数据访问技术和 XML 之间一直有着藕断丝连的关系。利用 ADO.NET 和 XmlDataDocument,我们很好地将这两者结合起来。我们可以执行直接从数据集读写 XML 的基本操作,当 XML 变得更加复杂时,我们可以通过一个链接的数据集将它加载到XmlDataDocument,这样就可以同时获得数据的 XML 和表格视图。

如今有许多查看和使用数据的方法。传统上,我们使用诸如 ADO.NET 这样的数据访问技术,这种技术为我们提供了主要针对关系数据的强大功能。利用 ADO 和 ADO.NET,可以实现与非关系数据的交互,但真正的焦点是传统的表格式数据视图。最近,我们看到了 XML 的崛起,这种技术的核心是分层数据表示形式。

多年以前,在我接触到关系数据库之前,我使用的是层次数据库。在许多方面,XML 可以说是层次数据库概念的开放表示形式,但因为 XML 是基于文本的,能够利用 XSL 应用转换,再加上其他与 XML 相关的标准,它在可扩展性和表现能力方面远远超出层次数据库。

利用 ADO.NET,Microsoft 不仅提供对关系数据源中的传统表格式数据视图的支持,而且还集成了对 XML 的强大支持。ADO.NET 和 XML 之间的关系以若干个级别存在于基础结构中,因此我们可以基于自己的应用程序要求以灵活的方式使用这两种数据技术。

在本文中,我将讨论 ADO.NET 与 XML 配合使用的基本知识。

*

ADO.NET 和 XML 的结构

在编写代码之前,我希望快速回顾一下 ADO.NET 与 XML 集成的方式。然后,我们通过构建一个说明这两种技术如何配合使用的测试应用程序,在实践中应用概念。

ADO.NET 本身可分为两个部分。一部分是托管提供程序,负责处理与数据源的通讯。另一部分是数据集,为表格式数据提供内存中的存储区。数据集可以加载来自托管提供程序的数据,或者可以直接从我们的代码加载。另外,数据集也可以通过使用 XmlReader 加载数据。上述内容如图 1 所示。

向数据集加载数据的三种方式。

1. 向数据集加载数据的三种方式。

无论使用哪种方式,最终结果都是包含数据表的数据集,而数据表又包含我们已经加载的数据。数据表是表格式结构,因此数据被表示为一组行和列。

利用数据集,我们可以通过代码操纵数据,或者,我们可以使用数据绑定将数据连接到用户界面 (UI) 中的控件。一旦数据填充到数据集,无论数据来自 Microsoft SQL Server? 还是 XML 文档,在行为上都没有什么不同。这一特点可以实现很强大的功能,因为它意味着,我们可以从表加载数据,然后将数据保存到 XML 文件,我们也可以从 XML 文件加载数据,然后将数据存储到数据库中的表。

使用 XmlReader 方法用 XML 填充数据集时,它将自动为 XML 数据生成一个定义,即 XSD。XSD 是在 XML 文档被读取时从它的结构派生出来的。这一点非常有用,因为它带来了另外一个好处,即您可以使用数据集迅速生成从现有的 XML 文档派生的 XSD。我很喜欢这个随带的好处,因为手动编写 XSD 是我最不喜欢做的事情之一。

另一方面,如果您希望控制从 XML 文件读到数据集中的字段,您可以创建自己的 XSD,在读取 XML 数据之前用它来初始化数据集。然后,在读取 XML 数据时,只有与 XSD 字段相匹配的数据被读入数据集。XML 文档中的任何其他数据都将被忽略。

需要说明的是 — 如果您读取数据时使用自定义的 XSD,数据集将只包含由该 XSD 定义的数据。如果您后来又将数据集另存为 XML,则只保留该 XSD 定义的数据。原来的 XML 文件中的任何其他数据都不会包括在输出中。

还有一点需要注意,当直接从数据集使用 XML 时,无论有没有预定义的 XSD,数据集不保留 XML 文档的格式设置。XML 文档内的任何空格、注释或其他非数据元素,都将在数据集写出 XML 时丢失。

为了解决数据丢失、格式设置和注释丢失这些问题,ADO.NET 提供了另一种使用 XML 的机制。

如果您以前曾经在 .NET 中使用过 XML,您一定很熟悉 XmlDocument 对象。XmlDocument 读取 XML 文档,允许我们对它进行操作,然后可以将它保存回去。该对象可以保留格式设置、注释和所有数据。

Microsoft .NET 还包括 XmlDataDocument 对象。XmlDataDocument 具有 XmlDocument 的所有功能,此外,它能理解如何与 ADO.NET 数据集集成。如图 2 所示。

XmlDataDocument 与 ADO.NET 数据集集成。

2. XmlDataDocument 与 ADO.NET 数据集集成。

这意味着我们可以同时使用数据集以表格格式和通过 XmlDataDocument 以分层格式查看数据。由于 XSD 定义了可以使用数据集的特定数据,因此我们可以准确地控制 XmlDataDocument 中的哪些数据可以通过数据集提供。

通过数据集视图,我们可以利用数据绑定的功能,使熟悉使用表格式数据的广大开发人员可以访问 XML 数据。XmlDataDocument 视图允许我们操作所有 XML 数据,包括数据集中不提供的任何数据。另外,XmlDataDocument 继续保留空格、格式设置和注释,因此当我们将文档保存回去的时候,我们将得到与读取时相比,属于同一类别的文件。

XmlDataDocument 和数据集之间的集成是双向的。一旦它们链接在一起,对数据集中的数据的任何更新将立即反映在 XmlDataDocument 中。同样,对 XmlDataDocument 中的数据的任何更改也将立即反映在数据集中。

除了能够直接读取 XML、将 XML 写到数据集中,以及能够将XmlDataDocument与数据集链接在一起,ADO.NET 还提供了强大的 XML 支持。了解上述背景后,我们可以研究并编写一些代码来说明 ADO.NET 如何与 XML 配合使用。

我们将对基于专栏文章ADO.NET and You中的应用程序进行构建。您可以从 MSDN 下载该代码。

该应用程序已经有一个 DataGrid、一个到 Pubs 数据库的连接,并且能够检索和更新 Authors 表中的数据。该应用程序还有一些按钮,您可以用它们将表中的数据写到文件,并将该文件读回数据库表。此示例中的文件不是 XML,而是简单文本文件。我们将对该应用程序进行扩展,使之使用 XML 文件。

直接在数据集中使用 XML

DataSet 对象包括直接读写 XML 文件的方法。这些方法的一个优点是,我们可以从数据库加载数据,然后将数据写到 XML 文件。现在我们来增强应用程序,以便利用这些方法。

将 XML 保存到文件

将一个按钮添加到名为 btnToXML 的 Form1。我们将编写此按钮后面的代码,将当前在 dsPubs 数据集中的任何数据保存为 XML 文件:

Private Sub btnToXML_Click(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles btnToXML.Click
DsPubs.WriteXml("c:\authors.xml")
End Sub

XML 将基于数据表中的列名自动生成,并且将包括数据集中的所有数据。如果数据集有一个以上的DataTable 对象,数据将包括在输出中。假设我们已经将 Authors 表中的数据加载到数据集,单击此按钮即可生成其内容如下所示的文件:

<?xml version="1.0" standalone="yes"?>
<dsPubs xmlns="http://www.tempuri.org/dsPubs.xsd">
<authors>
<au_id>172-32-1176</au_id>
<au_lname>White</au_lname>
<au_fname>Gerry</au_fname>
<phone>408 496-7223</phone>
</authors>
<authors>
<au_id>213-46-8915</au_id>
<au_lname>Green</au_lname>
<au_fname>Marjorie</au_fname>
<phone>415 986-7020</phone>
</authors>
</dsPubs>

数据集中的所有数据都会包括在内。从该 XML 可以大致看出输出的模式。XML 简单而直接,没有特定于数据集的信息或 XSD。如果我们需要数据的 XSD,可以通过下面的更改指定 XSD:

Private Sub btnToXML_Click(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles btnToXML.Click
DsPubs.WriteXml("c:\authors.xml")
DsPubs.WriteXml("c:\authors with schema.xml", _
XmlWriteMode.WriteSchema
End Sub

现在我们创建了两个文件。第一个文件包含简单的 XML 格式的数据,第二个文件包含以相同格式呈现的相同数据,但前面带有定义了数据结构的 XSD 信息。

通常,数据集自动生成 XSD。另一方面,我们也可以定义自己的 XSD,并在加载数据之前将它加载到数据集。在这种情况下,此 XSD 将反映我们预先确定的定义。稍后将研究加载自己的 XSD。

在我们的示例中,dsPubs 的 XSD 是我们在创建DataSet 对象时由 DataAdapter 向导自动生成的。以下是 XSD 的具体内容(部分文本已换行):

<?xml version="1.0" standalone="yes"?>
<dsPubs xmlns="http://www.tempuri.org/dsPubs.xsd">
<xs:schema id="dsPubs"
targetNamespace="http://www.tempuri.org/dsPubs.xsd"
xmlns:mstns="http://www.tempuri.org/dsPubs.xsd"
xmlns="http://www.tempuri.org/dsPubs.xsd"
xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-
microsoft-com:xml-msdata" attributeFormDefault="qualified"
elementFormDefault="qualified">
<xs:element name="dsPubs" msdata:IsDataSet="true">
<xs:complexType>
<xs:choice maxOccurs="unbounded">
<xs:element name="authors">
<xs:complexType>
<xs:sequence>
<xs:element name="au_id" type="xs:string" />
<xs:element name="au_lname" type="xs:string" />
<xs:element name="au_fname" type="xs:string" />
<xs:element name="phone" type="xs:string" />
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:choice>
</xs:complexType>
<xs:unique name="Constraint1" msdata:PrimaryKey="true">
<xs:selector xpath=".//mstns:authors" />
<xs:field xpath="mstns:au_id" />
</xs:unique>
</xs:element>
</xs:schema>

Author 数据紧跟在 XSD 的后面。注意,XSD 知道唯一主键值。这是因为 DataAdapter 向导是基于数据库定义生成 XSD 的。而从原始数据派生的 XSD 则不会包括这样的高级概念。

例如,如果您从 authors.xml 将 XML 加载到一般的 DataSet 对象,然后通过如下代码写出 XML(不输入此代码):

Dim dsXML As New DataSet()
dsXML.ReadXml("c:\authors.xml")
dsXML.WriteXml("c:\authors with schema.xml", _
XmlWriteMode.WriteSchema)

得到的 XSD 会更简单(部分文本已换行):

<?xml version="1.0" standalone="yes"?>
<dsPubs xmlns="http://www.tempuri.org/dsPubs.xsd">
<xs:schema id="dsPubs"
targetNamespace="http://www.tempuri.org/dsPubs.xsd"
xmlns:mstns="http://www.tempuri.org/dsPubs.xsd"
xmlns="http://www.tempuri.org/dsPubs.xsd"
xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-
microsoft-com:xml-msdata" attributeFormDefault="qualified"
elementFormDefault="qualified">
<xs:element name="dsPubs" msdata:IsDataSet="true">
<xs:complexType>
<xs:choice maxOccurs="unbounded">
<xs:element name="authors">
<xs:complexType>
<xs:sequence>
<xs:element name="au_id" type="xs:string" minOccurs="0" />
<xs:element name="au_lname" type="xs:string"
minOccurs="0" />
<xs:element name="au_fname" type="xs:string"
minOccurs="0" />
<xs:element name="phone" type="xs:string" minOccurs="0" />
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:choice>
</xs:complexType>
</xs:element>
</xs:schema>

这里再次提醒大家,Author 数据紧跟在 XSD 的后面。

注意,前面没有提到主键。这类信息是无法从简单的 XML 数据派生的。这种 XSD 只包括可以直接从数据本身推断出的基本信息。 我们可以增强代码的功能,不仅能够编写出 XML 及具有架构的 XML 的代码,还可以编写出架构本身的代码:

Private Sub btnToXML_Click(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles btnToXML.Click
DsPubs.WriteXml("c:\authors.xml")
DsPubs.WriteXml("c:\authors with schema.xml", _
XmlWriteMode.WriteSchema)
DsPubs.WriteXmlSchema("c:\authors.xsd")
End Sub

还要记住的是,由数据集编写出的 XML 和 XSD 都不包含注释或特殊格式设置,也不包含数据集中包含的数据之外的任何数据。

从文件读取 XML

将 XML 文件读到数据集的过程也同样简单。通过在窗体背后使用下面的代码,可将另一个按钮添加到名为 btnFromXML 的 Form1(窗体 1):

Private Sub btnFromXML_Click(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles btnFromXML.Click
DsPubs.Clear()
DsPubs.ReadXml("c:\authors.xml")
End Sub

ReadXml 方法从文件读取数据,然后将数据加载到数据集。

Clear 方法清除 dsPubs 中已有的任何数据,这一点很重要,因为将 XML 读到数据集与添加一组新行是一样的。由于我们使用的是利用 DataAdapter 向导生成的类型化数据集(如 dsPubs),数据集将强制执行诸如必须具有唯一主键这样的规则。因此,如果我们尝试从 XML 文件加载的数据与数据集中已有的数据相同,将导致运行时错误。

如果我们的 DataSet 对象是一般数据集,其表现将有所不同。例如,我们可以编写下面的代码(不输入此代码):

Dim dsXML As New DataSet()
dsXML.ReadXml("c:\authors.xml")
dsXML.ReadXml("c:\authors.xml")

其结果是两次包含所有数据的数据集。该数据集将自动生成数据的 XSD,但生成的 XSD 不知道唯一主键值。这样就可以多次加载相同的数据行。

使用正确的 XSD

我们已经看到数据集使用的 XML 定义(或 XSD)在行为上有很大的不同。我们的控制已超出数据集是否知道唯一主键这个问题。通过预定义数据集的 XSD,我们可以确切地控制哪些数据字段从源 XML 文件读取,从而确定哪些字段将在我们保存 XML 时被写出。

我们一直在使用的数据集 dsPubs 是使用 "ADO.NET and You" 一文中的 DataAdapter 向导生成的。这意味着它是一个类型化数据集,带有预定义的 XSD。我们可以在 Visual Studio? .NET 中使用其内置的 XSD 设计器编辑此 XSD。dsPubs.xsd 是解决方案资源管理器中的一个文件。通过双击打开设计器,如图 3 所示。

Visual Studio .NET 中的 XSD 设计器。

3. Visual Studio .NET 中的 XSD 设计器。

设计器在au_id 字段的旁边显示了一个钥匙图标,说明它就是唯一主键。设计器还列出可加载到数据集中的所有数据的字段名和数据类型。您可以使用此设计器来添加其他字段、删除字段或更改字段的数据类型。

有一点需要注意的是,当我们从SqlDataAdapter将数据加载到数据集时,这里显示的 XSD 将被忽略。举例来说,即使我们通过编辑它来删除Phone字段,当我们重新从数据库加载数据时,Phone 字段将还原,因为 XSD 将基于 SQL 定义被重新创建。

不过,当我们从 XML 文件将数据加载到数据集时,将忽略这里显示的 XSD。要了解具体过程,请在Phone 字段的左侧右击,然后从弹出式菜单中选择Delete。此操作将从 XSD 中删除Phone字段。

现在运行应用程序,然后单击From XML 按钮将 authors.xml 文件加载到数据集。注意,Phone字段没有显示在网格中,也没有加载到数据集中。该字段不匹配 XSD,因此被忽略了。

现在再回到 btnToXML 的代码,其中有一行代码将 XSD 从数据集写到一个名为 authors.xsd 的文件。Visual Studio .NET 也使用 XSD 设计器来编辑此文件。选择File | Open | File,然后选择 authors.xsd。该文件将在同样类型的设计器中打开,我们可以用图形方式编辑 XSD,或通过单击设计器底部的XML选项卡直接编辑。

为进行此演示,通过在该字段左侧右击后选择Delete,删除Phone 字段。您可能必须先用左键单击Phone 字段,来选择该字段,否则,Delete 操作将从 XSD 删除整个 Authors 实体。之后,保存该文件。

我们可以创建一个一般的数据集,并从诸如 authors.xsd 这样的文件将 XSD 加载到该数据集中。向名为 btnXSD 的 Form1 添加一个新按钮,并为一般数据集添加以下窗体级变量声明:

Private dsXML As DataSet

然后在新按钮后面加入以下代码:

Private Sub btnXSD_Click(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles btnXSD.Click
' Create a generic DataSet
dsXML = New DataSet()
' Load the XSD into the DataSet
dsXML.ReadXmlSchema("c:\authors.xsd")
' Load the XML data into the DataSet
dsXML.ReadXml("c:\authors.xml")
' Bind the grid to this new DataSet
DataGrid1.DataSource = dsXML.Tables(0)
End Sub

我们做的第一件事就是创建一个一般的DataSet对象。与具有相关联的 XSD 的类型化数据集 dsPubs 不同,这个新的数据集完全未定义,并且是空的,没有任何 XSD。

然后,我们将 XSD 文件 authors.xsd 加载到该数据集。这使得数据集拥有了数据的定义。在这种情况下,该定义知道主键字段,但由于我们删除了 Phone 字段,它并不知道该数据。

然后我们从 authors.xml 加载了 XML 数据本身。该文件的确包含 Phone 信息,但该信息被忽略了,因为它不匹配 XSD。接下来,DataGrid 被绑定到这个新的DataSet 对象,因此将显示结果。

使用自定义的 XSD 来加载数据时的数据显示。

4. 使用自定义的 XSD 来加载数据时的数据显示。

图 4 说明了为什么知道我们使用的是自动派生的 XSD(即 Visual Studio .NET 中的 DataAdapter 向导创建的 XSD),还是手动加载的 XSD 非常重要而且很有用。通过控制架构,我们可以控制加载的数据,以及数据在加载后的行为。

使用 XmlDataDocument 和数据集

到目前为止我们一直直接从数据集使用 XML。虽然这样做非常方便,而且功能相当强大,但也存在缺点。当我们混合使用 XML 数据源和非 XML 数据源时,那些技巧最有效。但我们如果读取和更新现有的 XML 文件,就会遇到限制。

尤其是,数据集只加载 XSD 架构为数据集定义的 XML 数据,忽略 XML 源中的所有其他数据。从数据集将数据写入 XML 时,只写入数据集中的数据,原来的 XML 文件中的任何其他数据将丢失。可能更糟的是,将数据读入数据集,然后再从数据集保存到 XML 文件时,原来的 XML 文件中的任何注释或特殊格式设置都会丢失。

为了克服这些限制,我们可以将XmlDataDocument 与数据集一起使用。XML 数据将加载到XmlDataDocument 对象,该对象将链接到一个数据集。我们可以直接通过XmlDataDocument 使用 XML 数据,或者,可以使用数据集以表格视图使用 XML 数据。

读取 XML 数据

在 Form1 中,为 Xml 命名空间添加Imports 语句:

Imports System.Xml

然后添加XmlDataDocument的窗体级变量声明:

Private xmlDoc As XmlDataDocument

现在,向名为 btnXMLdoc 的 Form1 添加一个按钮,该按钮将触发通过相关联的数据集将一个 XML 文档加载到XmlDataDocument 的过程:

Private Sub btnXMLdoc_Click(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles btnXMLdoc.Click
DsPubs.Clear()
xmlDoc = New XmlDataDocument(DsPubs)
xmlDoc.Load("c:\authors by hand.xml")
End Sub

首先我们要清除数据集,以确保它不包含任何数据。然后将DataSet 对象链接到一个新的XmlDataDocument 对象。此时,我们加载到XmlDataDocument的任何数据都将反映在数据集中。也就是说,是与定义数据集的 XSD 相匹配的任何数据。由于我们使用的是 dsPubs,我们知道它有一个相关联的 XSD,该 XSD 涵盖了我们一直在使用的 Author 数据。

为了了解具体过程,我们需要一个包括某些额外数据和一个注释的 XML 文档,这样就可以知道这类信息是如何保留的。 使用 Visual Studio .NET 打开 authors.xml 文件,然后编辑该文件,如下所示:

<?xml version="1.0" standalone="yes"?>
<dsPubs xmlns="http://www.tempuri.org/dsPubs.xsd">
<!-- a comment in the document -->
<authors>
<au_id>172-32-1176</au_id>
<au_lname>White</au_lname>
<au_fname>Gerry</au_fname>
<Phone>408 496-7223</Phone>
<Notes>Other data</Notes>
</authors>
<authors>
<au_id>213-46-8915</au_id>
<au_lname>Green</au_lname>
<au_fname>Marjorie</au_fname>
<Phone>415 986-7020</Phone>
<Notes>Other data</Notes>
</authors>
</dsPubs>

用名称 authors by hand.xml 保存此文件。注意,在这个新文件中,每个作者有一个Notes 元素,在 XML 中还有一个注释。

如果我们在运行应用程序后单击刚刚添加的按钮,数据集将被链接到新的XmlDataDocument 也将立即反映这些更改。同样,对XmlDataDocument 内的数据的任何更改将由数据集反映。

写入 XML 数据

一旦我们利用链接的数据集将数据加载到 XmlDataDocument,就可以通过这两个对象中的任何一个操作数据。操作数据后,我们很可能希望将它保存回 XML 文件。

如果我们使用数据集对象的WriteXml 方法来保存数据,我们会丢失数据集中没有显示的任何数据,以及文档中的任何注释。因此,我们可以将XmlDataDocument中的数据保存回文件,保留文档的所有数据、注释和格式设置。

通过下面的代码向名为 btnToXMLdoc 的 Form1 添加一个按钮:

Private Sub btnToXMLdoc_Click(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles btnToXMLdoc.Click
xmlDoc.Save("c:\authors by hand.xml")
End Sub

此代码设计为在我们单击 btnXMLdoc 后运行,将数据加载到XmlDataDocument 及其链接的数据集。

XmlDataDocument 的内容将保存回从中读取 XML 的文件。

如果我们运行应用程序、加载数据、编辑 DataGrid 中的字段,然后单击按钮,更改的数据将写回该文件。下面的 XML 是更改 DataGrid 中的作者的姓名、然后再更新 XML 文件的结果:

<?xml version="1.0" standalone="yes"?>
<dsPubs xmlns="http://www.tempuri.org/dsPubs.xsd">
<!-- a comment in the document -->
<authors>
<au_id>172-32-1176</au_id>
<au_lname>White</au_lname>
<au_fname>Gerold</au_fname>
<Phone>408 496-7223</Phone>
<Notes>Other data</Notes>
</authors>
<authors>
<au_id>213-46-8915</au_id>
<au_lname>Green-Black</au_lname>
<au_fname>Marjorie</au_fname>
<Phone>415 986-7020</Phone>
<Notes>Other data</Notes>
</authors>
</dsPubs>

注意,注释和 <Notes> 元素保留下来,数据已被更新。上面的示例说明了应该怎样同时直接利用 XmlDocument 和 ADO.NET 数据集方法的功能来使用数据。

小结

在本文中,我讨论了在 ADO.NET 中使用 XML 的基本知识。过去,传统的数据访问技术和 XML 之间一直有着藕断丝连的关系。利用 ADO.NET 和 XmlDataDocument,我们很好地将这两者结合起来。我们可以执行直接从数据集读写 XML 的基本操作,当 XML 变得更加复杂时,我们可以通过一个链接的数据集将它加载到XmlDataDocument,这样就可以同时获得数据的 XML 和表格视图。uthors> <authors> <au_id>213-46-8915</au_id> <au_lname>Green-Black</au_lname> <au_fname>Marjorie</au_fname> <Phone>415 986-7020</Phone> <Notes>Other data</Notes> </authors> </dsPubs>

注意,注释和 <Notes> 元素保留下来,数据已被更新。上面的示例说明了应该怎样同时直接利用 XmlDocument 和 ADO.NET 数据集方法的功能来使用数据。

小结

在本文中,我讨论了在 ADO.NET 中使用 XML 的基本知识。过去,传统的数据访问技术和 XML 之间一直有着藕断丝连的关系。利用 ADO.NET 和 XmlDataDocument,我们很好地将这两者结合起来。我们可以执行直接从数据集读写 XML 的基本操作,当 XML 变得更加复杂时,我们可以通过一个链接的数据集将它加载到XmlDataDocument,这样就可以同时获得数据的 XML 和表格视图。

posted on 2006-04-26 08:02  落拓孤鸿  阅读(1038)  评论(0编辑  收藏  举报