传统的ADO与ADO.NET最大的区别在于以ADO.NET存储的RowSets有真正的相互关系。例如,一个DataSet存储包含客户信息的Customers表和客户订单的Orders表,在ADO.NET中,这些表之间可以具有联系,并且重新创建了在关系数据库中的联系。对ADO.NET来说,一旦你再次得到这两个行集合的数据(换句话说,有父子关系的表的数据),并且使他们之间具有联系,你就可以通过给定的Parent来访问所有Rows,同时显示任何一个表(any one DataTable in a grid),或者修改表对象的几行数据,并将修改批量更新到数据库。DataRelation对象最为ADO.NET应用的一部分,能实现这些功能。
本栏,我将探讨通过ADO.NET在多层次结构中(相对单一的RowSet(通过INNER JOIN))呈现数据上正反两方面的意见。我会解释DataRelation对象如何提高应用程序的性能,并讨论实现他们应该做些什么 。我会通过一些Demo来做说明,这些Demo创建了通过DataRelation对象产生联系的一系列DataTale对象,就能访问Parent和Children RowSets,并及联更新整个DataSet。
为什么使用
DataRelation
?
在我转入任何代码事例之前,理解数据关系带来了什么和在你运用它之前需要考虑些什么是很重要的。关键因素包括是否运用数据关系,如何使用他们来带来数据冗余(how to use them involve the issue of data redundancy.),应用程序需要如何显示数据,应用程序需要如何管理数据更改。开始我会挑几点并演示如何实现关系。
在2002年2月的MSDN Magazine 的Data Point栏目中,我演示了如何去扩展应用程序,这样就避免了使用ADO.NET DataRelation对象到处对多级数据层次结构进行修改的麻烦。在那个栏目中我所演示的样例是一个在WebForm中使用DataGrid控件来显示存储数据的标准应用。(DataGrid控件对使用关系数据表DataTable对象是理想的,因为它内置了对这些关系的支持。)本栏我会将重点放在DataRelation对象上并解释使用它们相对于使用传统的SQL连接(join)语句的优缺点。
本栏的例子中,我将涉及到Northwind数据库的customer-to-orders-to-order具体的层次结构。在考虑是使用单一的包含连接(join)的数据表还是使用一系列的有关系的数据表时,这里有一些关键考虑的事项要记住。管理数据的两个基本功能是显示数据和管理数据修改(both to the database and within the data structure itself)。
如果你想简单的显示数据,并不要求任何数据的更改,那么选择就主要取决于你想显示单个表格还是多个。如果数据只用来显示单个表格,那么使用连接存储数据最适合。只是因ADO.NET能存储多个有关联的数据表,并不是意味开发者必须每次都使用他们。就象使用Phillips的螺丝起子拧不一定象使用普通的螺丝起子拧一样有效,关联数据表可能在特定情况下不是最有效的方案。如果你的目的只是显示数据的话,使用一系列的表格优于使用单一的表格来显示,并存储到关联的表中,这样更适合。(and you would prefer to display the data in a series of grids rather than in a single grid,storing the data in releated DataTable objects is more appropriate.)
仅显示数据这种情况是普遍的,那么要求显示和数据更改的应用就不同了。对这类应用来说,拥有显示数据的合适工具和管理数据更改的能力是至关重要的,这里就是ADO.NET真正灵活的地方。通过将客户,订单和订单详细信息存储到三个分开的彼此关联的表中,你就可以轻松的在表格中显示它们,并管理每个表的任何数据更改。关联行集rowset的DataRelation对象会在通过GetChanges方法将数据更改保存到底层数据库之前会被过滤掉,这样就只有更改的数据被保存到了数据源。
连接查询和分割查询
使用DataTable和DataRelation对象关联的行集RowSet也可以帮助消除的冗余数据,这些数据对单一的连接行集来说是本身固有的。例如,连接cutomers,orders,order details表为单一的行集,这对每个订单和订单详情行来说就复制了指定的客户数据(例如,客户的名字及公司名称)。例如,假如一个客户有5个订单,没个订单有5个订单详情,那么这个客户数据会有25行数据信息。对于使用SQL的开发者来说这太可怕了,然而使用层次数据结构如XML和ADO.NET关系数据表对象是消除这些冗余数据的好方法。
最普遍的行类型来源于连接查询。例如,查询客户及其他们的订单,使用SQL语法会放回一个单一的行集。这个行集为每条客户订单都复制客户信息,因此产生了数据冗余。再加入订单详情到这个连接查询的话,将产生更多的数据冗余。(Figure 1)
Joining Customers, Orders, and Order Details
![](https://i-blog.csdnimg.cn/blog_migrate/6810355c2f78c12e91b7997a8e8c583a.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/6810355c2f78c12e91b7997a8e8c583a.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/6810355c2f78c12e91b7997a8e8c583a.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/6810355c2f78c12e91b7997a8e8c583a.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/6810355c2f78c12e91b7997a8e8c583a.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/6810355c2f78c12e91b7997a8e8c583a.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/6810355c2f78c12e91b7997a8e8c583a.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/6810355c2f78c12e91b7997a8e8c583a.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/6810355c2f78c12e91b7997a8e8c583a.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/6810355c2f78c12e91b7997a8e8c583a.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/6810355c2f78c12e91b7997a8e8c583a.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/6810355c2f78c12e91b7997a8e8c583a.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/6810355c2f78c12e91b7997a8e8c583a.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/6810355c2f78c12e91b7997a8e8c583a.gif)
更新一个来源于多个表的行集是非常麻烦的,如果你想更新parent数据。例如,如果你想更新customer-to-orders-to-order details行集的customer数据的话,由于冗余数据,你必须重新查询数据库确保行集中的所有行都被更新。使用ADO.NET中的DataRelation对象时,这个行集会被插入基行集并相互关联起来,使合成后的DataSet跟实际结构更接近。通过将查询插入相互关联的三个数据表的关系结构中,消除冗余数据。更新数据变的更容易,现实客户及其订单也更自然。
使用关联数据表另一个优点是在更新数据库时能增加灵活性。当数据被分别放在customers,orders,order details数据表中并通过DataRelation对象相互关联起来时,每个数据表的数据都能对其中的数据使用指定的命令来更新数据到数据库。例如,包含customer行集的数据表能将修改了的数据保存到数据库,使用根据Northwind数据库的customers表而定的SqlCommand对象。这个SqlCommand对象可能涉及SQL语句或存储过程,它与用于更新数据表更改到数据源的SqlDataAdapter对象联合使用。本栏的后面窝会演示如何使用这些对象并使用SQL Server 2000 Northwind数据库提供的样例代码将更改存入层次结构中。(make modifications to a hierarchical structure)
数据修整
在XML程序员迷恋于层次数据结构的年代里,ADO.NET就已经将它在实现基于Microsoft.NET的应用中变的很简单了。在搜索每个应用中实现层次数据结构中,以前的ADO版本已经有个称为数据修整(Data Shaping)的技术了。当数据修整表示层次结构时,还存在一些很严重的缺点一致没有得到认可(it alse has some drawbacks significant enough that it hasn’t caught on in the mainstream),而且,为使用数据修整你必须学习SHAPE语法。
当数据修整要求专用的数据提供程序和语法的时候,ADO.NET中的层次数据结构就能用标准的我们都熟悉并喜爱的SQL提供程序和SQL语法来创建了。数据修整和其他传统ADO的XML特征都被添加到左右,使得XML变得很受欢迎,并被使用,然而,ADO本身并没有构建XML,因此它有个根本性的缺点,这个缺点在ADO.NET中必须予以解决。而传统的ADO的XML特点,诸如数据形成和存储XML是很有用的,ADO.NET中的XML特点是迄今最全面的。我从来没有听过一个单身人士说,他们曾去想念数据整形语法。
定义数据关系
如何使用
DataRelation
定义层次
rowset
呢?让我们来看看,
DataSet
包含了客户,及其它们的订单,和他们的订单详情的能使用
DataRelation
对象将他们关连起来三个独立的数据表。使用
DataSet
包含通过
DataRelation
对象连接在一起的数据表,可以级联更新,浏览
parent
纪录,送不同的数据源连接数据,甚至不用在数据库中就可以整合数据。第一步,创建存储三个独立的源于三个独立查询的
RowSet
的
DataSet
对象
(Figure 2)
。
Creating a DataSet
![](https://i-blog.csdnimg.cn/blog_migrate/6810355c2f78c12e91b7997a8e8c583a.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/6810355c2f78c12e91b7997a8e8c583a.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/6810355c2f78c12e91b7997a8e8c583a.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/6810355c2f78c12e91b7997a8e8c583a.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/6810355c2f78c12e91b7997a8e8c583a.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/6810355c2f78c12e91b7997a8e8c583a.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/6810355c2f78c12e91b7997a8e8c583a.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/6810355c2f78c12e91b7997a8e8c583a.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/6810355c2f78c12e91b7997a8e8c583a.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/6810355c2f78c12e91b7997a8e8c583a.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/6810355c2f78c12e91b7997a8e8c583a.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/6810355c2f78c12e91b7997a8e8c583a.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/6810355c2f78c12e91b7997a8e8c583a.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/6810355c2f78c12e91b7997a8e8c583a.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/6810355c2f78c12e91b7997a8e8c583a.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/6810355c2f78c12e91b7997a8e8c583a.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/6810355c2f78c12e91b7997a8e8c583a.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/6810355c2f78c12e91b7997a8e8c583a.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/6810355c2f78c12e91b7997a8e8c583a.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/6810355c2f78c12e91b7997a8e8c583a.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/6810355c2f78c12e91b7997a8e8c583a.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/6810355c2f78c12e91b7997a8e8c583a.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/6810355c2f78c12e91b7997a8e8c583a.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/6810355c2f78c12e91b7997a8e8c583a.gif)
在
Figure 2
中的
Code
创建一个包含所有客户的
DataTable
,和包含所有订单的第二个
DataTable
和包含所有订单详情的第三个
DataTable
的
DataSet
。在这一点上,
code
定义了
DataSet
包含了三个
RowSet
,但是关系还没确定。为关联
DataTable
,我使用两个
DataRelation
对象:一个用来关联客户和他们的订单,另一个用来关联订单和他们的详情。
Figure 3
显示了如何为这些情况创建
DataRelation
对象,在
Figure 2
种构建
code
样例。
Creating DataRelation Objects
![](https://i-blog.csdnimg.cn/blog_migrate/6810355c2f78c12e91b7997a8e8c583a.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/6810355c2f78c12e91b7997a8e8c583a.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/6810355c2f78c12e91b7997a8e8c583a.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/6810355c2f78c12e91b7997a8e8c583a.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/6810355c2f78c12e91b7997a8e8c583a.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/6810355c2f78c12e91b7997a8e8c583a.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/6810355c2f78c12e91b7997a8e8c583a.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/6810355c2f78c12e91b7997a8e8c583a.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/6810355c2f78c12e91b7997a8e8c583a.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/6810355c2f78c12e91b7997a8e8c583a.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/6810355c2f78c12e91b7997a8e8c583a.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/6810355c2f78c12e91b7997a8e8c583a.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/6810355c2f78c12e91b7997a8e8c583a.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/6810355c2f78c12e91b7997a8e8c583a.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/6810355c2f78c12e91b7997a8e8c583a.gif)
通过创建DataRelation对象,并添加到DataSet的Relations集合中,三个DataTable对象的RowSet通过定义的字段相互关联起来。象大多数ADO.NET对象一样,DataRelation对象有多个不同的构造函数。我使用接受关系名,父表的列和子表的列的构造函数。假如定义关系的多个列,可以传给一个父表列的集合和子表列的集合。
另一种选择是使用相同的三个参数中的第一个参数,我在
Figure 3
中使用过,然后传入第四个参数表示约束是否被自动创建(传入一个布尔值)。但是立刻会有更多的约束。
DataSet
一旦被三个
RowSet
和关联
DataTable
对象的关系填充时,
DataSet
就可以轻松的使用
DataGrid
显示在
Web
页面上,只要象下面一样设置一下
DataSource
属性:
![](https://i-blog.csdnimg.cn/blog_migrate/6810355c2f78c12e91b7997a8e8c583a.gif)
DataGrid很聪明,他可以判断有多个需要显示数据表,并且它应该允许RowSet按DataRelation对象规定的规则被操纵。
约束和级联
就像数据库,DataSet对象中的外键约束帮助约束数据完整性。当我创建在Figure 3种创建Customer2OrderDataRelation时,自动创建了父表主键(customer.customerid)的唯一性约束和子表外键(order.customerid)约束。外键约束会在DataSet呈现到WEB页面的DataGrid时自动起作用(be in effect)。因此,如果你试图修改子纪录的外键值为一个父表主键中不存在的值时,会产生一个违背完整性约束的错误。为演示这个,你应该通过设置DataSet的EnforceConstraints属性为false来取消约束,以使他们不会被强制。除为证明起见,我不推荐这样做,因为这将让你的用户违反数据和应用的完整性。
通过外键支持级联更新和删除是SQL Server 2000的新特征。例如,如果你开启了SQL Server 2000中的这些功能并且你删除父表中的一行,相关联的子表的行也会被删除。ADO.NET有类似的功能,这些功能可以通过ForeignKeyConstraint对象的DeleteRule和UpdateRule属性来控制。默认的,这些规则用于对关联数据进行级联修改。因此如果你改变了客户表的CustomerID的值,那么订单表中的CustomerID也会随着更新。然而,通过设置属性为Rule的枚举值来改变它们,Rule枚举值有Cascade(默认的),None,SetDefault,或者SetNull。
为创建自己的外键约束,你可以定义
ForeignKeyConstraint
对象。
这可以让你灵活的定义列和约束规则,然后运用约束。下面的代码片断演示了怎样建立一个
ForeignKeyConstraint
和并和
DataRelation
联合:
![](https://i-blog.csdnimg.cn/blog_migrate/6810355c2f78c12e91b7997a8e8c583a.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/6810355c2f78c12e91b7997a8e8c583a.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/6810355c2f78c12e91b7997a8e8c583a.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/6810355c2f78c12e91b7997a8e8c583a.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/6810355c2f78c12e91b7997a8e8c583a.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/6810355c2f78c12e91b7997a8e8c583a.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/6810355c2f78c12e91b7997a8e8c583a.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/6810355c2f78c12e91b7997a8e8c583a.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/6810355c2f78c12e91b7997a8e8c583a.gif)
父与子
当你有一个
DataRelatin
对象
,他的重要之处是知道如何向上和向下浏览父和子的
RowSet
。例如,你想要循环遍历特定客户的订单,你要取出这些订单,由于
DataRelation
已经被你确定了。
Figure 4
演示了如何取出所有子表的行并遍历他们。
Looping Through Child Rows
![](https://i-blog.csdnimg.cn/blog_migrate/6810355c2f78c12e91b7997a8e8c583a.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/6810355c2f78c12e91b7997a8e8c583a.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/6810355c2f78c12e91b7997a8e8c583a.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/6810355c2f78c12e91b7997a8e8c583a.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/6810355c2f78c12e91b7997a8e8c583a.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/6810355c2f78c12e91b7997a8e8c583a.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/a41954a27d6ad96fa2c2cf816e677448.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/1327ab569c1ae82736693a50b8e33378.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/6a9c071a08f1dae2d3e1c512000eef41.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/6a9c071a08f1dae2d3e1c512000eef41.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/6a9c071a08f1dae2d3e1c512000eef41.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/6a9c071a08f1dae2d3e1c512000eef41.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/6a9c071a08f1dae2d3e1c512000eef41.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/0196c3df5ea9e936f21e9932cca91014.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/6810355c2f78c12e91b7997a8e8c583a.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/6810355c2f78c12e91b7997a8e8c583a.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/6810355c2f78c12e91b7997a8e8c583a.gif)
Figure 4还显示了如何取出第一个客户的所有儿子并遍历他们。但你想找到父表行或某数据表的行。(But what if you want to find the parent row or rows of a DataTable?)如果有可能多于一行,你可以使用GetParentRows方法。然而,对于我的例子来说,每个订单只有一个父行,因此我使用GetParentRow方法。这个代码显示如何通过Order DataTable的第一行的引用取得父表Customer DataTable的值:
DataRow oRow
=
oDs.Tables[
"
Order
"
].Rows[
0
].GetParentRow(oDr_Customer2Order);
MessageBox.Show(oRow[
"
CompanyName
"
].ToString());
![](https://i-blog.csdnimg.cn/blog_migrate/6810355c2f78c12e91b7997a8e8c583a.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/6810355c2f78c12e91b7997a8e8c583a.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/6810355c2f78c12e91b7997a8e8c583a.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/6810355c2f78c12e91b7997a8e8c583a.gif)
结论
另一个值得注意的特征是ADO.NET如何使自己的XML这样容易。ADO.NET把XML作为他的基础,因此你可以通过ADO.NET将DataSet转化到XML。使用DataSet的WriteXml,WriteXmlSchema,GetXml,和GetXmlSchema方法,你可以取得放在(represent)背后DataSet结构下的XML。在本栏的例子中使用GetXml方法会产生一个包含XML的字符串。GetXmlSchema方法取得数据的模式而GetXml方法取得数据和模式:
MessageBox.Show(oDs.GetXml());
你也可以使用ReadXml和ReadXmlSchema方法从一个XML字符串或文件创建DataSet。例如,你可以使用WriteXml方法保存一个包括结构和数据的DataSet为XML文件,然后,你可以使用ReadXml方法从相同的XML文件创建DataSet。 记住 ADO.NET 关系的关键点是,首先你该确定你是否需要,关系是非常适合应用在需要层叠表示的数据。然而,如果你的情况,包括以单表格格式显示数据,一系列相关 RowSet 可能不能为单一的连接 RowSet 带来什么好处。在以后的栏目中,我会在 ADO.NET 和 SQL Server 的 expression-based 栏目进一步介绍 DataRelation 对象,如何执行批量更新,通过 SqlCommandBuilder 来找捷径,并发行和一致性的考虑。