Visual Studio "Whidbey"的对象空间初识
探索了.Net环境下的对象/关系映射框架—对象空间的特性
原作:
Dino Esposito
Wintellect
February 2004
翻译:
刘海东
2004-9-20
适用于:
Microsoft® Visual Studio® code-named "Whidbey"
Microsoft® ADO.NET
Microsoft® SQL Server™ 2000
SQL Language
摘要:对象空间是微软VS中最有趣的新特性,代号Whidbey.它是一个与微软Ado.Net和微软.Net技术完全集成的一个对象/关系映射工具,对象空间在你的商业层和物理库中原始数据之间增加了一个抽象层.你只管考虑设计程序要使用的对象,由对象空间负责通过使用sql语句从数据源中读写数据.
提示: Whidbey在微软2003.10的开发大会上提供了预览代码.
内容:
基本介绍
对象空间概述
从表映射到类
得到数据对象
持久层变动
与对象图合作
延迟加载
对象空间的好处
在设计.Net程序的数据访问层(DAL)时,通常由两种选择来建立商业表示层和DAL自己之间的双向交互.第一个选择是写一个类从Ado.Net对象中的读入和读出数据.第二个选择是用一个类来抽象底层的表,然后再根据需要添加逻辑和程序特性.这两种情况下,程序负责结构化数据在各层之间的传送,并使用SQL指令读写数据.
在前一种情况下,主要的代码是要把表中的字段与用户界面的元素绑定—在写.Net程序时这一动作是非常方便的.这样做最大的缺点是需要写大量的SQL语句来处理数据,如果程序规模增加,会变得非常复杂和难以维护,这种程序的设计是以数据为中心.
选择更加抽象和面向对象并不能完全摆脱这种情况.面向对象模型使你有了强壮的商业逻辑层,但还需要有一个层复杂把对象持久化到存储介质.而且这一附加层必须与存储介质的语法完全隔离开.
面向对象的设计在纸上是幽雅而整洁的,但是实现起来却要花费大量的时间.尽管如此,当一个大型程序与管理紧密相关时,层次化的数据和对象化的术语是十分有帮助的,并且这也往往是你能顺利完成项目的唯一出路.正因为如此,对象关系映射(O/RM)工具已经出现了很久,并且已有多家供应商提供这一技术.
一个O/RM系统使你能把一个存在的对象持久化到指定的存储介质.所以你需要一个O/RM系统来帮助你持久化对象,但你并不需要创建对象使用这一O/RM系统.你只要定义对象如何与物理的表和字段的映射关系,由O/RM来负责查询和更新表中的数据.
对象空间是集成在Whidbey中的一个O/RM框架,它提供了一组类来处理SQL Server 2000 和 SQL Server Yukon数据的读写操作.对象空间引擎会把对象查询转换为对表的查询并把对象中属性的修改转换为对表的修改.返回调用所获取的数据都会先被转换成.Net的类,下面的图是对象空间的一个大概描述
图1:基于对象空间的程序整体架构
通过设计,商业规则描述了程序逻辑并控制问题域中各种实体间的相互作用.商业规则会形成一些与特定商业实体吻合的对象术语如:客户,订单,发票等等,而不仅仅是一些数据集和表.
对象空间让开发者关注于商业实体,从而设计由合理对象组成的程序而不是一组数据流.对象空间只要求简单的映射类和数据表.之后由对象空间引擎处理数据源操作并把你从操作细节中解脱出来.因此,你只要用灵活的,可重用的和可扩充的对象来组成程序,在这同时,你就可以把数据存放在关系数据库中了.
对象空间的架构位于程序逻辑和数据源之间,它使开发者能管理数据而不需要对数据的物理存储了解太多知识.通过使用对象空间,你持久化数据和读取数据都不需要写SQL代码.
对象空间架构的根类是ObjectSpace. ObjectSpace类处理与数据源的信息交互并管理查询和获取数据源发生的动作.对象空间负责持久化对象到数据表并实例化查询的结果.为了使对象空间动作,需要映射结构到ADO.Net连接对象.映射模式可以是XML文件这样的静态资源,也可以通过MappingSchema对象的接口动态建立.
MappingSchema对象决定了哪些字段和表会被用于持久化对象数据,又应该从哪些字段和表中读取数据.下面的代码片断演示了如何实例化一个对象空间:
Dim conn As SqlConnection = New SqlConnection(ConnString)
Dim os As ObjectSpace = New ObjectSpace("myMappings.xml", conn)
这里的连接对象只是一个普通的数据连接,其中包含了连接一个SQL Server实例的参数.
映射模式分为3个部分:关系模式定义(RSD),对象模式定义(OSD),和映射模式,它负责连接前面两种模式.为了方便,你可以把每种模式放到独立的XML文件.在这种情况下,只有映射模式文件必须传递到对象空间的创建方法中.在映射模式中会引用关系模式和对象模式.
下面的清单中显示的映射模式文件绑定了一个包含Nothwind数据库表的关系模式文件(rsd.xml)和一个对象模式文件(osd.xml).
<m:MappingSchema
xmlns:m="http://schemas.microsoft.com/data/2002/09/28/mapping">
<m:DataSources>
<m:DataSource Name="NorthwindRSD" Type="SQL Server"
Direction="Source">
<m:Schema Location="RSD.XML" />
<m:Variable Name="Customers" Select="Customers" />
</m:DataSource>
<m:DataSource Name="DataTypesOSD" Type="Object" Direction="Target">
<m:Schema Location="OSD.XML" />
</m:DataSource>
</m:DataSources>
<m:Mappings>
<m:Map SourceVariable="Customers" TargetSelect="Samples.Customer">
<m:FieldMap SourceField="CustomerID" TargetField="Id" />
<m:FieldMap SourceField="CompanyName" TargetField="Company" />
<m:FieldMap SourceField="ContactName" TargetField="Name" />
<m:FieldMap SourceField="Phone" TargetField="Phone" />
</m:Map>
</m:Mappings>
</m:MappingSchema>
在上面XML文件的<Mappings>内定义了数据表和一个.Net类的绑定关系,其中详细设定了Customer表和Customer类的映射.SourceField属性是一个表的列,TargetField是类的属性.例如CustomerID列绑定到ID属性.
源数据的关系模式定义如下:
<rsd:Database Name="Northwind" Owner="sa"
xmlns:rsd="http://schemas.microsoft.com/data/2002/09/28/rsd">
<r:Schema Name="dbo"
xmlns:r="http://schemas.microsoft.com/data/2002/09/28/rsd">
<rsd:Tables>
<rsd:Table Name="Customers">
<rsd:Columns>
<rsd:Column Name="CustomerID" SqlType="nchar" Precision="5" />
<rsd:Column Name="CompanyName" SqlType="nvarchar"
Precision="40" />
<rsd:Column AllowDbNull="true" Name="ContactName"
SqlType="nvarchar" Precision="30" />
<rsd:Column AllowDbNull="true" Name="Phone" SqlType="nvarchar"
Precision="24" />
</rsd:Columns>
<rsd:Constraints>
<rsd:PrimaryKey Name="PK_Customers">
<rsd:ColumnRef Name="CustomerID" />
</rsd:PrimaryKey>
</rsd:Constraints>
</rsd:Table>
</rsd:Tables>
</r:Schema>
</rsd:Database>
如你所见,这个关系模式只选了Customer表中的一小部分列,在列的清单中还包含了主键.
对象模式定义文件如下:
<osd:ExtendedObjectSchema Name="DataTypesOSD"
xmlns:osd="http://schemas.microsoft.com/data/.../persistenceschema">
<osd:Classes>
<osd:Class Name="Samples.Customer">
<osd:Member Name="Id" Key="true" />
<osd:Member Name="Company" />
<osd:Member Name="Name" />
<osd:Member Name="Phone" />
</osd:Class>
</osd:Classes>
</osd:ExtendedObjectSchema>
The file describes a class like the one shown below.
Namespace Samples
Public Class Customer
Public Id As String
Public Name As String
Public Company As String
Public Phone As String
End Class
End Namespace
综上所述,映射信息指示对象空间如何将Samples.Customer对象的数据与Customer表之间如何串行化和反串行化.
下面我们将演示如何用Customer类读写数据,在开始之前,先看ObjectClass中定义了哪些方法:
方法 |
描述 |
BeginTransaction |
开始一个数据源的事务 |
Commit |
提交数据源中的当前事务 |
GetObject |
从数据源中返回查询字串中指定类型的对象 |
GetObjectReader |
从数据源中返回查询字串中指定类型的对象流 |
GetObjectSet |
从数据源中返回查询字串中指定类型的对象集合 |
MarkForDeletion |
标记指定的对象被删除,数据源中的这个对象记录(和关联的数据记录) 在PersistChanges Marks执行后将被删除 |
PersistChanges |
关联操作, 删除, 更新, 和删除更新到数据源中 |
Resync |
从数据库中重新读取数值并刷新对象状态 |
Rollback |
回滚数据源中的当前事务 |
StartTracking |
标记对象为持久对象,开始准备对数据源进行I/O操作 |
这些方法可以被分为3个类别—交易的,读取的,写入的. BeginTransaction, Commit, Rollback都是事务相关的方法.
读取的方法是Resync, GetObject, GetObjectReader, 和 GetObjectSet.这些GetXXX方法返回映射类的实体对象.这些方法的不同在于返回的对象数量和数据连接的状态. GetObject和 GetObjectSet在内部会调用GetObjectReader读取数据.
GetObject 只从结果集中返回第一个对象,如果有多个数据对象返回将丢出异常.
GetObjectReader 返回对象的流,一个ObjectReader对象与ADO.Net中的DataReader十分相象,返回数据后保持连接,在关闭ObjectReader后数据联接会立即关闭.
GetObjectSet 返回非连接的数据对象集合,返回类型是ObjectSet,与DataAdapter的Fill方法类似.
程序对象和数据源的映射是如何发生的呢?让我们先看一下这些方法的参数
Function GetObject(t As Type, query As String) As Object
Function GetObjectReader(t As Type, query As String) As ObjectReader
Function GetObjectSet(t As Type, query As String) As ObjectSet
所有函数的第一个参数Type是数据对象对应的类名,下面是示例代码片断:
Dim dataSet As ObjectSet
dataSet = os.GetObjectSet(GetType(Customer), "Id = 'ALFKI'")
系统会根据查询条件字串产生针对数据源的查询对象,在GetObjectSet先取得结果集的对象流,经过处理后把不同的行分别装载到不同的对象,然后返回一个对象的集合并关闭数据库连接。
需要重点说明的是,所有通过GetXXX方法取回的对象,都会自动地被对象空间引擎追踪其变化。不需要调用StartTracking方法把它们附加到对象空间引擎。Resync方法能取到一个或一组对象,并通过运行一条新的查询及时更新它们的数据。
跟写操作相关的方法有StartTracking,MarkForDeletion和PersistChanges。StartTracking把对象标示为持久对象。当调用这个方法后,对象会被赋予一个状态值,并被加入到对象空间系统的环境变量中用以追踪其数据变化。对象被追踪的状态与Ado.Net中DataRow的RowState类似,当调用PersistChanges方法后,根据状态表示该对象是否应该被添加,删除还是更新到数据源中去。
下面通过一个实例来详细说明在对象空间的架构下如何使用这些方法:
为了在你的Whidbey程序中使用对象空间,需要引用两个文件,System.Data.ObjectSpaces 和 System.Data.SqlXml。如果要方便,你也可以在源码中直接导入System.Data.ObjectSpaces名称空间。
Imports System.Data.ObjectSpaces
在把程序中所用的模式文件复制到与程序执行文件的相同目录后,使用以下代码:
Dim ConnString As String = "..."
Dim conn As New SqlConnection(ConnString)
Dim os As New ObjectSpace("map.xml", conn)
Dim query As New ObjectQuery(GetType(Customer), "Id = 'ALFKI'")
Dim reader As ObjectReader = os.GetObjectReader(query)
For Each c As Customer In reader
CustID.Text = c.Id
CustName.Text = c.Name
CustCompany.Text = c.Company
CustPhone.Text = c.Phone
Next
reader.Close()
以上是一个Windows Form程序的代码片断,它显示一个带有文本框的窗口――客户编号,姓名,公司和电话号码。所运行的查询是一个ObjectQuery类的实例。这个类的创建方法需要对象的类型和一个查询字串。对象的类型指定了与数据源交换信息的商业对象。必须说明的是查询字串要根据持久对象属性的名称来写,不是数据库中的字段名。上面的这个查询字串将返回ID属性等于ALFKI的客户对象。
用于查询对象的新语言叫做OPath,它类似于XPath,使你能用面向对象的语法来写查询语句。
这个查询对象被传给GetObjectReader方法并返回一个基于流的ObjectReader对象。一个ObjectReader的内容可以用for..each来读取返回的客户对象。
For Each c As Customer in reader
' Process information here
Next
既然把数据库中每一行的数据映射到了用户定义的类实例,把类的属性再绑定到用户界面的元素就是小菜一碟了。下图显示了上面演示代码的效果
PersistChanges方法负责把对象空间监控的对象变化写回到数据库中去。用GetXXX方法取回的对象已经被自动追踪变化,但新建的对象呢?下面的代码演示如何新增一个对象到系统中:
Dim c As New Customer
c.Name = "Belinda Newman"
c.Company = "Litware, Inc."
c.Phone = "(425) 707-9790"
c.Fax = "(425) 707-9799"
os.StartTracking(c, InitialState.Inserted)
这里创建并填充了一个新的对象,然后用StartTracking方法把它加入到对象空间。调用这个方法需要一个初始状态值来标示对象是新增的还是已经存在的。InitailState只有两种值Inserted和Unchanged。当调用PersistChange方法后,标示为Inserted的对象将会在数据库中新增一条对应的记录。
os.PersistChanges(c)
注意这里的对象中有一个Fax的属性,在前面的模式文件中并没有把它映射打表的字段。(结果到底怎样,作者没有明确说明)
对象空间也能处理复杂的分层主从结构,常见的比方说客户与订单之间的一对多关系。假设改进过后的客户类别如下:
Public Class Customer
Public Id As String
Public Name As String
Public Company As String
Public Phone As String
Public Fax As String
Public Orders As ArrayList = New ArrayList()
End Class
新增的订单属性将包含客户的订单,读取客户和订单的代码如下:
Dim reader As ObjectReader
Dim oq As New ObjectQuery(GetType(Customer), "Id = 'ALFKI'", "Orders")
reader = os.GetObjectReader(oq)
For Each c As Customer In reader
OutputCustomer(c)
For Each o As Order in c.Orders
OutputOrder(o)
Next
Next
reader.Close()
在以上的代码里隐藏了两个重要的变化,一个是客户和订单之间的映射模式,一个是ObjectQuery的创建参数。
在这个例子中,ObjectQuery创建时使用了3个参数:返回的对象类型,Opath查询字串和范围字串。范围字串是一组用逗号隔离的关联对象标示。这里指定范围的值是Orders,保证与客户相关的订单都将被返回。
而程序用到的映射模式文件中,在<DataSource>区域中增加了<RelationShip>节点:
<m:DataSource Name="NorthwindRSD" Type="SQL Server" Direction="Source">
<m:Schema Location="hRSD.XML" />
<m:Variable Name="Customers" Select="Customers" />
<m:Variable Name="Orders" Select="Orders" />
<m:Relationship Name="Customers_Orders"
FromVariable="Customers" ToVariable="Orders">
<m:FieldJoin From="CustomerID" To="CustomerID"/>
</m:Relationship>
</m:DataSource>
<RelationShip>中定义了客户和订单用CustomerID字段来关联。
在对象模式文件中也要增加新节点<ObjectRelationships>。这个节点描述了父类(客户)和子类(订单)之间的关系
<osd:ObjectRelationships>
<osd:ObjectRelationship Name="Customers_Orders" Type="OneToMany"
ParentClass="Customer" ParentMember="Orders"
ChildClass="Order" ChildMember="Customer" />
</osd:ObjectRelationships>
为了改进在主从关系中内存消耗,对象空间提供了一个特性“延迟装载”。在一对多和一对一中都可以使用,其设计思想是从对象数据只有在被请求时才被装载。
其中ObjectList提供一对多对象的延迟装载功能,ObjectHolder提供一对一关系的延迟装载。要访问实际的对象,ObjectList是通过其InnerList属性,ObjectHolder是通过InnerObject,它们的类型都是Object。
为了能通过强制类型来访问延迟装载的对象,可以用指定类型对它们进行封装。在下面的代码中,订单属性是在ObjectList中,但访问属性时InnerList被映射为它的实际类型OrderList。
Public Class Customer
Private m_Orders As ObjectList = New ObjectList()
Public Property Orders As OrderList
Get
Return m_Orders.InnerList
End Get
Set (ByVal Value As OrderList)
m_Orders = Value
End Set
End Property
:
End Class
为了使延迟装载属性正确映射到对象空间,你可以在OSD映射文件中使用LazyLoad属性。
<osd:Member Name="m_Orders" Alias="Orders" LazyLoad="true" />
而且,OSD需要建立关系的成员属性是私有的,不是公共的。
<osd:ObjectRelationship Name="Customers_Orders" Type="OneToMany"
ParentClass="Customer" ParentMember="Orders"
ChildClass="Order" ChildMember="m_Orders" />
延迟装载的属性,只有当它被程序中的代码第一次访问时才会加载数据。而且,对象空间不会隐含刷新延迟装载的数据,但你可以用Fetch强制刷新数据。
虽然离最终完成还有很远,对象空间无疑是Whidbey中最有趣的新特性。它是一个完全集成于ADO.Net和.NET技术的O/R映射工具。它在你的应用程序中增加了一个抽象层,隔离了商业逻辑层和物理数据访问层。并且对象空间的编程模型和ADO.NET十分接近,有利于.NET程序员的学习和掌握。
对象空间,以及其它类似的类库框架,会给程序带来部分负载并要求开发人员对编程模型有一定了解。如今,认为对象空间需改进的方面主要还有以下几个:调试跟踪系统地效率较低,缺少辅助映射工具,SQL语句地质量有待提高,负载性能较低。
顺便提一句,Visual Studio Whidbey还是一个离正式发行很远地产品,目前还没有进入公开测试地阶段。现在还只仅仅是开始,让我们拭目以待这项技术地早日发布。
Dino Exposito是意大利罗马的一个讲师和咨询师,Wintellect小组的成员之一。专注于ASP.Net和ADO.Net技术,大部分时间用于在欧美的授课和咨询。另外,还负责Wintellect的ASP.Net和ADO.Net课件制作,在MSDN杂志撰写“前沿”专栏。
本文地址:http://com.8s8s.com/it/it43113.htm