一个多层的Client/Server应用程序在逻辑上划分为几个部分,分别在不同的机器上运行,这些机器既可以在一个局域网内,也可以在Internet上。多层体系结构最大的优势可以概括为两点,一是集中化的商业逻辑,另一个是客户程序可以做得很“瘦”。
目前较常见的是三层的体系结构,其中,最关键的是应用服务器,它在三层体系结构中起了承上启下的作用,所以,应用服务器又叫Data Broker。Delphi4可以创建应用服务器,也可以创建“瘦”客户。如果不怕麻烦的话,也可以创建数据库后端。
在更复杂的多层体系结构中,“瘦”客户与远程服务器之间可以加入更多的服务中间件,例如,可以加入一个安全服务中间件,或者加入一个转换中间件,专门用来处理不同平台共享数据的问题。一旦您真正理解了三层的体系结构,多层的体系结构就迎刃而解。
3.1 多层体系结构的概述
Delphi 4对多层体系结构的支持主要得益于它的MIDAS技术。MIDAS是Multi-tier Distributed Application Services Suite的简称。MIDAS技术与Delphi 4中的另一个关键技术DAX配合起来使用,可以使多层的体系结构分布在Intrenet/Intranet上。
3.1.1 多层体系结构的优势
在多层体系结构中,由于服务器集中实现了应用逻辑(又称商业规则),客户程序可以把重点放在显示数据和与用户交互上,客户程序甚至都不需要知道数据存储在哪儿。
具体来说,多层的体系结构具有如下优势:
在一个共享的中间层封装了商业规则。不同的客户程序可以共享同一个中间层,而不必由每个客户程序单独实现商业规则。
客户程序可以做得很“瘦”。因为很多复杂的工作由应用服务器代劳了,客户程序只需要关注用户界面本身。“瘦”客户程序更容易发布、安装、配置和维护。
实现了分布式数据处理。把一个应用程序分布在几个机器上运行,可以提高应用程序的性能,通过冗余配置还可以保证不会因为局部故障导致整个应用程序崩溃。
有利于安全。可以把一些敏感的功能放在有严密防护措施的层上,同时又不至于使用户界面变得复杂。Delphi 4中的CORBA或MTS支持较复杂的安全机制。
3.1.2 MIDAS技术
MIDAS技术是多层体系结构的关键。无论是应用服务器端还是客户端,MIDAS技术需要有DBCLIENT.DLL的支持,这个动态链接库用于管理数据包。发布MIDAS应用程序时需要购买服务器许可。
基于MIDAS的多层应用程序需要用到一些特殊的构件,这些构件分为四大种类:对象库中的远程数据模块。远程数据模块与普通的数据模块有些相似,不同的是,远程数据模块可以作为COM服务器或CORBA服务器让客户程序访问它的接口。
TDataSetProvider和TProvider构件。这两个构件用在应用服务器端,主要作用是提供IProvider接口,客户程序通过IProvider接口获得数据和更新数据集。
TClientDataSet构件。这是一个从TDataset继承下来的但不需要BDE的构件。MIDAS连接构件。包括TDCOMConnection、TSocketConnection、TCorbaConnection TOLEnterpriseConnection、TMIDASConnection和TRemoteServer。其中,TMIDASConnection和TRemoteServer是为了兼容Delphi3的代码而保留的。MIDAS连接构件的作用是为客户程序定位服务器和IProvider接口。每个MIDAS连接构件都以一种特定的通讯协议工作。
3.1.3 MIDAS应用程序是怎样工作的
用户首先要启动客户程序,客户程序将试图连接应用服务器,如果应用服务器还没有运行,客户程序将激活应用服务器,并从中获得IProvider接口。
客户程序向应用服务器请求数据。如果TClientDataSet的FetchOnDemand属性设为True,客户程序会根据需要自动检索附加的数据包如BLOB字段的值或嵌套表的内容。否则,客户程序需要显式地调用GetNextPacket才能获得这些附加的数据包。
应用服务器收到客户程序的请求后,就从远程数据库服务器那儿检索数据,并打包返回给客户程序
客户程序收到数据包后把包打开,然后显示或进行处理。
用户对数据进行编辑修改,然后向应用服务器申请更新数据,实际上也要打包。
应用服务器收到客户程序的申请后,就向远程数据库服务器申请更新数据。如果出错,应用服务器就把出错的记录返回给客户程序去核对。
客户程序核对并修改了数据后,既可以放弃此次更新,也可以继续此次更新。
3.1.4 客户程序的结构
对于最终用户来说,多层体系结构中的客户程序与两层体系结构中的应用程序没有什么区别,在结构上,客户程序就好像一个基于文件的单层应用程序一样,仍然通过标准的数据控件与用户交互。但与单层应用程序不同的是,多层体系结构中的客户程序是通过应用服务器提供的IProvider接口获得数据的,也通过IProvider接口申请更新数据。
注意:当使用MTS的时候,可以选择不使用IProvider接口。不使用IProvider接口的好处是,可以充分发挥MTS在处理事务方面的特长。
在客户程序中,MIDAS连接构件扮演着极其重要的角色。不同的MIDAS连接构件使用不同的通讯协议:
. TDCOMConnection DCOM
. TSocketConnection Windows Sockets (TCP/IP)l
. TOLEnterpriseConnection OLEnterprise (RPCs)
. TCorbaConnection CORBA (IIOP)
TRemoteServer和TMIDASConnection是为了兼容Delphi 3的代码而保留的。
3.1.5 应用服务器的结构
应用服务器的关键部件是远程数据模块,它提供了IDataBroker接口。当客户程序与应用服务器建立了连接,就通过IDataBroker接口来获得IProvider接口。
Delphi 4支持三种类型的远程数据模块:
TremoteDataModule。这是一个支持双重接口的自动化服务器,这种类型的远程数据模块适合于使用DCOM、TCP/IP或OLEnterprise方式。
TMTSDataModule。这也是一个支持双重接口的自动化服务器,用这种类型的远程数据模块创建的应用服务器是Active Library即动态链接库,适合于使用DCOM、TCP/IP或OLEnterprise方式。
TcorbaDataModule。这是CORBA服务器,适用于与CORBA客户通讯。
上述三种远程数据模块都可以作为容器,但只能放置非可视的构件。另外,远程数据模块上一般要放一个或几个TDataSetProvider或TProvider构件来提供IProvider接口 。
远程数据模块上也可以放TDatabase构件和TSession构件。
3.1.6 MTS
MTS是Microsoft Transaction Server的简称,是Microsoft为分布式环境下进行事务处理所设计的服务接口。使用TMTSDataModule类型的远程数据模块的优势是:
MTS为应用服务器提供了基于角色的安全机制。每个客户都扮演着一种角色,决定了他们能否访问远程数据模块的接口。TMTSDataModule有一个函数叫IsCallerInRole,可以用来检查客户的角色,然后有条件地开放该角色所允许的功能。
MTS提供了缓冲池的功能,它能把与数据库的连接放到池中,当一个客户不再需要连接时,另一个客户可以继续使用它,这样,应用服务器不必再次登录到远程数据库服务器。可能有的读者会想到,这个功能非常类似于TDatabase构件的KeepConnection属性。不过,要注意的是,如果用了TDatabase构件的话,KeepConnection属性最好设为False。
MTS提供了强大的事务处理能力,它的“两阶段提交”技术使得应用程序能够跨服务器处理事务。
可以用TMTSDataModule类型的远程数据模块实现一个MTS服务器,这个MTS服务器能够根据需要自动地激活或相反,换句话说,只有当远程数据模块接收到客户的连接请求时才创建模块的一个实例,这样能够最大程度地节省资源。
由此可见,MTS服务器可以有两种工作方式,一是单实例方式,一个实例能够处理多个客户的请求,不过,如果客户较多的话,远程数据模块就成了瓶颈,制约着应用服务器的性能。二是多实例方式,每个客户请求连接时都会创建远程数据模块的一个实例,这样,几个客户就可以同时访问数据库而不需要排队。
为了发挥MTS的上述优势,远程数据模块的实例必须做到与状态无关,而IProvider接口又依赖于状态信息,这就造成冲突。因此,TMTSDataModule类型的远程数据模块往往不用IProvider接口,而是自己创建一个接口来传递数据和申请更新。
注意:使用MTS的时候,在远程数据模块的实例激活之前不能连接数据库。
3.1.7 IDataBroker接口和IProvider接口
应用服务器上的远程数据模块支持IDataBroker接口,当客户程序与应用服务器连接以后,客户程序上的MIDAS连接构件就查找IDataBroker接口。
IDataBroker接口只实现了一个方法叫GetProviderNames,调用这个方法可以获得一个列表,这个列表列出了应用服务器上的TDataSetProvider和TProvider构件。
TClientDataSet的ProviderName属性可以指定其中一个TDataSetProvider或TProvider构件。当客户程序通过IDataBroker接口的GetProviderNames以及TClientDataSet的ProviderName属性指定了应用服务器上的一个TDataSetProvider或TProvider构件后,只要客户还在引用IProvider接口,远程数据模块的状态就应该保持,这与MTS的许多特点是有冲突的,也会与单实例的CORBA服务器发生冲突。
客户程序与应用服务器之间通过IProvider接口交换数据,不过,大部分客户程序并不直接使用IProvider接口,而是通过TClientDataSet的属性和方法间接地使用IProvider接口。 不过,也可以通过Provider属性获得IProvider接口,然后直接访问IProvider接口。
下面这个表列出了IProvider接口的属性和方法,同时列出了TProvider构件以及TClientDataSet构件中与之对应的属性和方法。
IProviderTProviderTClientDatasetApplyUpdatesApplyUpdatesApplyUpdatesConstraints属性Constraints客户程序只能通过IProvider接口访问这个属性DataDataDataDataRequestDataRequest客户程序只能通过IProvider接口访问这个方法Get_ConstraintsConstraints客户程序只能通过IProvider接口访问这个方法Get_DataGet_Data用于实现Data属性GetMetaDataGetRecords(Count = 0)内部使用GetRecordsGetRecords用于GetNextPacketResetReset内部使用Set_ConstraintsConstraints客户程序只能通过IProvider接口访问这个属性SetParamsSetParams用于Params属性
注意:IProvider接口的许多属性和方法依赖于远程数据模块的状态信息,正因为如此,在使用CORBA或MTS的应用程序中一般不要用IProvider接口。
3.2 选择连接方式
在客户程序与应用服务器之间,Delphi 4提供了四种不同类型的连接方式或者说通讯协议,包括DCOM、TCP/IP、OLEnterprise和CORBA。这些不同的连接方式都各有利弊,到底选择哪种连接方式,取决于客户的数量、客户的分布情况以及怎样发布应用程序。
DCOM是一种最直接的连接方式,它不需要专门的运行期软件支持。不过,Windows 95 不支持DCOM,除非安装了DCOM95程序。
要使用MTS安全服务,最好使用DCOM连接方式。MTS的安全服务是基于角色的,当一个客户通过DCOM访问MTS时,DCOM会告诉MTS有关客户的信息,MTS据此来决定客户的角色。如果用其他连接方式,需要有专门的运行期软件支持,客户的调用首先被传递给这些运行期软件而不是MTS,MTS就不能尽快指派角色。
TCP/IP连接方式的适合范围非常广泛,例如,如果客户程序要以ActiveForm的形式分布在Web上,最好采用TCP/IP连接方式,因为您无法肯定下载ActiveForm的计算机是否支持DCOM,而支持TCP/IP的环境是很普遍的。
要使用TCP/IP连接方式,应用服务器端必须运行一个专门的运行期软件ScktSrver.exe或ScktSrvc.exe,其中,ScktSrvc.exe只适合于Windows NT,可以作为一个服务在后台运行。与DCOM连接方式不同的是,客户的请求首先传递给ScktSrver.exe或ScktSrvc.exe,然后再创建远程数据模块的实例,而不是由客户的调用直接创建远程数据模块的实例。客户程序上的MIDAS连接构件通过IProvider接口与ScktSrvr.exe or ScktSrvc.exe通讯。
不过,客户程序很有可能在没有正常释放对IProvider 接口的引用之前出现异常,而TCP/IP连接方式无法检测到这种情况,更无法通知应用服务器,因此,有可能造成应用服务器上的资源被占用后得不到释放的后果。
如果要在应用服务器端使用Business Object Broker,就要使用OLEnterprise连接方式。此时,应用服务器端和客户端都要安装OLEnterprise运行期软件。
Delphi 4是目前唯一支持CORBA的开发工具。基于CORBA的客户程序和应用服务器可以与其他基于CORBA的应用程序无缝对接。要使用CORBA连接方式,需要ORB的支持,它提供了类似于Business Object Broker的功能。
3.3 创建应用服务器的一般步骤
要创建一个多层Client/Server应用程序,首先要创建应用服务器,然后注册或安装应用服务器,只有应用服务器已注册并且正在运行的情况下,才能创建客户程序。对于客户程序来说,既可以在设计期连接应用服务器,也可以在运行期连接应用服务器。
注意:如果客户程序与应用服务器不在同一个系统中,必须在客户计算机上注册或安装应用服务器,这样,在设计期就可以连接应用服务器。
创建一个应用服务器与创建一个两层的数据库应用程序有些相似,主要的区别是,应用服务器需要提供IProvider接口,这一般是通过TDataSetProvider或TProvider构件提供的,也可以通过数据集构件如TTable的Provider属性提供。创建应用服务器的一般步骤是:
第一步是使用"File"菜单上的"New Application"命令开始一个新项目,然后使用File菜单上的New命令,选取Multi页,如图3.1所示。
选择一个远程数据模块。如果要创建一个COM自动化服务器,允许客户通过DCOM、TCP/IP、OLEnterprise等方式访问此服务器,选择RemoteMod。如果要创建一个允许客户通过MTS访问的Active Library,选择MTSData Module。如果要创建一个CORBA服务器,选择Corba Data。
第二步是把一个数据集构件如TTable、TQuery或TStoredProc放到远程数据模块上,并进行有关设置,使得它们能访问远程的SQL数据库。尽量不要把TDatabase构件放到远程数据模块上,因为这可能引起名称冲突。如果实在要用TDatabase构件来连接SQL数据库,建议把TDatabase构件放到另一个数据模块上,然后引用这个数据模块的单元文件。
第三步是把TDataSetProvider或TProvider构件放到远程数据模块上,有一个数据集构件,就要有一个TDataSetProvider或TProvider构件与之对应。然后,用鼠标右键单击TDataSetProvider或TProvider构件,在弹出的菜单中选择ExportFrom Name in Data Module命令,这是为了引出Provider接口,在类型库中注册。
第四步是设置TDataSetProvider或TProvider构件的DataSet属性指定要访问的数据库,实际上就是第二步所放的数据集构件。
第五步是编写代码,实现商业规则。当然,这一步远远不是几句话所能说清楚的。
第六步是保存、编译、注册或安装应用服务器。
如果使用DCOM、TCP/IP、OLEnterprise作为通讯协议,应用服务器就好像一个自动化服务器一样,必须像ActiveX或COM服务器那样注册。
如果使用MTS,应用服务器是DLL而不是EXE,这时候不需要注册应用服务器,而要把这个DLL作为MTS对象安装到MTS包中。
如果使用CORBA,可以不注册但最好注册。如果要使客户程序对服务器接口的调用在运行期是动态确定的,就要在接口库(Interface Repository)中安装服务器的接口。如果要使客户程序能自动激活应用服务器(如果还没有运行的话),应用服务器就必须用OAD(Object Activation Daemon)注册。
第七步是如果应用服务器没有使用DCOM,您必须安装有关的运行期软件,因为其他连接方式需要这些运行期软件的支持。例如,对于TCP/IP来说,需要安装ScktSrvr.exe或ScktSrvc.exe,后者只能运行在Windows NT环境下。对于OLEnterprise来说,需要安装OLEnterprise运行期版本。对于CORBA来说,需要安装VisiBroker ORB。
3.4 远程数据模块
应用服务器的关键部件是远程数据模块。Delphi 4支持三种类型的远程数据模块,分别是TRemoteDataModule、TMTSDataModule、TCorbaDataModule。
3.4.1 TRemoteDataModule
要加入一个TRemoteDataModule类型的远程数据模块,使用“File”菜单上的“New”命令,选取“Multitier”页,双击“Remote Data Module”图标,弹出“Remote Data Module Wizard”对话框,如图3.2所示。
在“Class Name”框内键入远程数据模块的类名,不必以T打头。Delphi 4将以此名生成一个TRemoteDataModule的派生类,并以此名生成有关接口。例如,假如在“Class Name”框内键入“MyDataServer”, 远程数据模块的类名就是TMyDataServer,它所实现的接口叫IMyDataServer,其祖先接口是IDataBroker。
在“Threading Model”框内选择一种线程模式。可以选“Single-threaded”、“Apartment-threaded”、“Free-threaded”或者“Both”。
在“Instancing”框内选择是否根据客户的请求生成远程数据模块的多个实例,可以选“Single instance”或“Multiple instance”。
3.4.2 TMTSDataModule
要加入一个TMTSDataModule类型的远程数据模块,使用“File”菜单上的“New”命令,选择“Multitier”页,双击“MTS Data Module”图标,弹出“MTSData Module Wizard”对话框,如图3.3所示。
图3.3 MTS Data Module对话框
在“Class Name”框内键入远程数据模块的类名,不必以T打头。Delphi 4将以此名生成一个TMTSDataModule的派生类,并以此名生成有关接口。例如, 假设在“Class Name”框内键入“MyDataServer”, 远程数据模块的类名就是TMyDataServer,它所实现的接口叫IMyDataServer,其祖先接口是IDataBroker。
对于TMTSDataModule类型的远程数据模块来说,必须在“ThreadingModel”框内选择一种线程模式。可以选“Single”、“Apartment”或者“Both”。在“Transaction Attributes”框内选择事务属性:
如选择“Requires a transaction”,每当客户访问远程数据模块的接口时,都与当前的事务是相关的。客户不可能在事务中再申请一个新的事务。
如选择“Requires a new transaction”,每当客户访问远程数据模块的接口时,都自动开始一个新的事务。如选择“Supports transactions”,远程数据模块可以用在事务的环境中,客户访问远程数据模块的接口时必须申请一个新的事务。
如选择“Does not support transactions”,远程数据模块不能用在事务的环境中。
注意:MTS对象只能加入到ActiveX项目中,如果试图在一个EXE项目中加入TMTSDataModule类型的远程数据模块,Delphi 4会显示一个提示框,如图3.4所示。
图3.4 一个提示框
3.4.3 TCORBADataModule
要加入一个TCorbaDataModule类型的远程数据模块,使用“File”菜单上的“New”命令,选取“Multitier”页,双击“CORBA Data Module”图标,弹出“CORBA Data Module Wizard”对话框,如图3.5所示。
图3.5 CORBA Data Module对话框
在“Class Name”框内键入远程数据模块的类名,不必以T打头。Delphi 4将以此名生成一个TCorbaDataModule的派生类,并以此名生成有关接口。例如,假设在“Class Name”框内键入“MyDataServer”, 远程数据模块的类名就是TMyDataServer,它所实现的接口叫IMyDataServer,其祖先接口是IDataBroker。
在“Instancing”框内指定应用服务器怎样创建远程数据模块的实例,可以选“Shared Instance”或者“Instance-Per-Client”。
如果选“Shared Instance”,应用服务器只创建远程数据模块的一个实例来处理所有客户的请求,因此,远程数据模块必须与状态无关,换句话说,就是不能使用IProvider接口。
如果选“Instance-Per-Client”,每当一个客户试图连接时,远程数据模块都会生成一个实例。只要客户与应用服务器的连接没有断开,远程数据模块的实例就一直存在。这种模式下,允许使用IProvider接口。唯一要考虑的问题是,客户程序有可能意外终止,导致没有正常地断开与应用服务器的连接。应用服务器为了避免不必要的资源浪费,可以定期地检查客户是否正在运行,如没有,就手工把远程数据模块的实例删掉。
在“Threading Model”框内选择一种线程模式。可以选“Single-threaded”、“Multi-threaded”。
3.5 Provider
远程数据模块上往往要放一个或几个TDataSetProvider或TProvider构件,用于提供IProvider接口。有时候,也可以不显式地使用TDataSetProvider或TProvider构件,而是由数据集构件如TTable、TQuery或TStoredProc的Provider属性间接地提供IProvider接口。
显式地使用TDataSetProvider或TProvider构件的好处是,可以直接控制数据包中包含哪些信息、应用服务器怎样响应客户的请求。如果显式地使用了TDataSetProvider或TProvider构件,必须设置他们的DataSet属性指定要访问的数据集。
3.5.1 控制数据包中的字段
要控制哪些字段包含到数据包中,首先要创建永久字段。以后,只有永久字段才加入到数据包中。如果不创建永久字段的话,数据集中的所有字段都将加入到数据包中。
如果创建的永久字段中包含计算字段,由于计算字段的值是在运行期计算出来的,这些字段虽然也能加入到数据包中,但这些字段传递到客户端后就变成只读的。
由于客户程序很有可能要编辑修改数据,并且要把编辑修改后的数据申请更新到应用服务器上,因此,您创建的永久字段的数量不能太少,否则,很有可能出现重复的记录。举例来说,假设有一个学生成绩表,由学号、姓名、语文成绩、数学成绩、历史成绩等字段组成,如果创建的永久字段中只包含语文成绩、数学成绩、历史成绩等字段,很有可能出现两名学生的上述成绩完全一样,也就是说有重复的记录,这是不允许的。
如果实在不想使客户程序看到某个字段,而如果没有这个字段的话很有可能出现上述错误,这时候您可以让这个字段(TField对象)的ProviderFlags属性包含pfHidden元素,表示这个字段虽然加入到数据包中,但却是隐含的,客户看不到它。
特别要注意的是,如果使用TQuery作为应用服务器上的数据集构件,SQL语句应当选择足够多的字段,即使客户程序并不需要这么多字段,否则,就有可能出现上述错误。
3.5.2 Options属性
这个属性是一个集合,用于设置有关打包和传递的选项。
如果包含poFetchBlobsOnDemand元素,表示BLOB字段一般不放到包中,除非客户端的TClientDataSet构件的FetchOnDemand属性设为True或者显式地调用FetchBlobs。
如果包含poFetchDetailsOnDemand元素,表示嵌套表中的字段不放到包中,除非客户端的TClientDataSet构件的FetchOnDemand属性设为True或者显式地调用FetchDetails。
如果包含poIncFieldProps元素,表示把字段的属性也放到包中,包括Alignment、MinValue、DisplayLabel、DisplayWidth、Visible、DisplayFormat、MaxValue、EditFormat、Currency、EditMask、DisplayValues等属性。
如果包含poCascadeDeletes元素,当父表中的某条记录被删除时就把子表中的相应记录也删除。
如果包含poCascadeUpdates元素,当父表的关键字段的值变化时自动更新子表的记录。
如果包含poReadOnly元素,表示不允许“瘦”客户向TDataSetProvider申请更新数据。
3.5.3 在数据包中加入自定义的信息
当客户端通过IProvider 接口调用DataRequest函数请求数据时将在应用服务器端触发OnGetDataSetPropertiesevent事件,这样,应用服务器就有机会在数据包中加入一些自定义的信息。客户端可以调用GetOptionalParam来检索这些信息。
OnGetDataSetPropertiesevent事件是这样声明的:
TGetDSProps = Procedure(Sender: TObject; DataSet: TDataSet; out Properties:OleVariant);
其中,Properties参数是一个可变类型的数组,用于指定要加入的信息。Properties参数的每个元素由三部分组成:名称、值和一个布尔数。Delphi 4定义了几个标准的信息名称,它们是UNIQUE_KEY、DEFAULT_ORDER、CHANGE_LOG、SERVER_COL、CONSTRAINTS、DATASET_CONTEXT、DATASET_DELTA、LCID、BDERECORD_X、TABLE_NAME、MD_FIELDLINKS、UPDATEMODE。程序示例如下:
Procedure TAppServer.Provider1GetDataSetProperties(Sender: TObject; DataSet: TDataSet; out Properties:OleVariant);
Begin
Properties := VarArrayCreate([0,1], varVariant);
Properties[0] := VarArrayOf(['TimeProvided', Now, True]);
Properties[1] := VarArrayOf(['TableSize', DataSet.RecordCount, False]);
End;
上面这个程序中,加入了两个自定义的信息,一个叫TimeProvided,它的值是当前的日期和时间,True表示这个信息可以由客户端返回给应用服务器。另一个信息叫TableSize,它的值是数据集的记录数,False表示这个信息不可以由客户端返回给应用服务器。
以后,当客户端申请更新数据时,TDataSetProvider的OnUpdateData事件可以读出数据包中的信息。程序示例如下:
Procedure TAppServer.Provider1UpdateData(Sender: TObject; DataSet: TClientDataSet);
var WhenProvided: TDateTime;
Begin
WhenProvided := DataSet.GetOptionalParam('TimeProvided');
...
End;
3.5.4 响应客户的数据请求
在大多数的多层应用程序中,客户请求数据是自动进行的,应用服务器对客户请求的响应也是自动的,它自动地检索数据、把数据打包,然后把数据包传递给客户。
应用服务器在把数据包传递给客户之前,还有机会对其中的数据进行编辑 加工,例如,可以对其中敏感的数据加密,或者基于某种条件删掉一些记录。
TDataSetProvider或TProvider构件的OnGetData事件可以让您实现上述功能,程序示例如下:
Procedure TDBClientTest.ProviderGetData(DataSet: TClientDataSet);
Begin
With DataSet Do
Begin
While not EOF Do
Begin
Edit;
SensitiveData.AsString := DoEncrypt(SensitiveData.AsString);
Post;
Next;
End;
End;
End;
3.5.5 响应客户的更新请求
客户程序通过调用ApplyUpdates 向应用服务器申请更新数据。当应用服务
器上的TDataSetProvider或TProvider构件收到客户的更新请求后,就会触发OnUpdateData事件,这样您就有机会编辑数据包(Delta属性)。退出处理OnUpdateData事件的句柄后,TDataSetProvider或TProvider构件就会把数据更新到远程服务器上。
更新是一条记录一条记录进行的。每一条记录被更新前的一瞬间将触发BeforeUpdateRecord事件,这样您还有机会对数据进行检查和修改。如果出现错误,就会触发OnUpdateError事件。发生错误的原因通常是数据违反了服务器的纠错规则,或者另一个客户程序也修改了记录,而且正好在前一个客户已经申请更新的时候。
上述错误既可以由应用服务器来处理,也可以回传给客户处理。有些错误可能需要用户的介入,这就要客户端在处理。
3.5.6 在更新数据库之前编辑Delta数据包
当应用服务器端调用ApplyUpdates向远程服务器申请更新数据时将触发OnUpdateData事件,这样,应用服务器就有机会对将要更新的数据进行检查,也可以对数据进行修改。OnUpdateData事件是这样声明的:
TProviderDataEvent = Procedure(Sender: TObject; DataSet: TClientDataSet) of object;
其中,DataSet参数代表客户程序上的TClientDataSet构件,这样就可以访问Delta属性得到当前要更新的数据包。另外还有一个重要的属性需要访问,这就是UpdateStatus属性,这个属性表示Delta数据包的更新类型。程序示例如下:
Procedure TDataModule1.Provider1UpdateData(Sender:TObject;DataSet: TClientDataSet);
Begin
With DataSet Do
Begin
First;
While not Eof Do
Begin
If UpdateStatus = usInserted then
Begin
Edit;
FieldByName('DateCreated').AsDateTime := Date;
Post;
End;
Next;
End;
End;
End;
3.5.7 怎样定位记录
在处理OnUpdateData事件的句柄中,除了可以检查和修改Delta数据包外,还可以设置怎样定位记录或者说把哪些记录更新到服务器上。
默认情况下,应用服务器用自动生成的SQL UPDATE、INSERT或DELETE语句来把Delta数据包中写到远程服务器中,例如:
UPDATE EMPLOYEES
Set EMPNO = 748, NAME = 'Smith', TITLE = 'Programmer 1', DEPT = 52
WHERE
EMPNO = 748 and NAME = 'Smith' and TITLE = 'Programmer 1' and DEPT = 47
除非另外指定,否则,数据包中的所有字段都将出现在UPDATE子句和WHERE部分,换句话说,就是用所有的字段去定位一条记录。不过,也可以有选择地排除一些字段,这就要用到UpdateMode属性。这个属性可以设为以下值:
.upWhereAll所有字段都用来定位记录;
.upWhereChanged只有关键字段和变化了的字段用来定位记录;
.upWhereOnly只有关键字段用来定位记录。
不过,UpdateMode属性只能区分关键字段,但实际应用往往要复杂得多。例如,可能不想更新EMPNO字段,而且不想让TITLE和DEPT字段出现在WHERE部分,这时候就要用到字段(TField对象)的ProviderFlags属性,此属性是一个集合,可以包含下列元素:
.pfInWhere该字段将不出现在自动生成的INSERT、DELETE和UPDATE语句的WHERE部分;
.pfInUpdate该字段将不出现在自动生成的UPDATE语句的UPDATE子句;
.pfInKey这个字段将出现在因为更新失败而执行的SELECT语句的WHERE部分,SELECT语句用于选择出错的记录的当前值;
.pfHidden这个字段将用来定位字段,但对客户端是隐藏的。
下面这个程序示例把EMPNO字段排除在UPDATE子句之外,把TITLE字段和DEPT字段排除在WHERE部分之外。
Procedure TDataModule1.Provider1UpdateData(Sender: TObject; DataSet: TClientDataSet);
Begin
With DataSet Do
Begin
FieldByName('EMPNO').UpdateFlags := [ufInUpdate];
FieldByName('TITLE').UpdateFlags := [ufInWhere];
FieldByName('DEPT').UpdateFlags := [ufInWhere];
End;
End;
3.5.8 在服务器端纠错
大多数数据库管理系统(RDBMS)都实现了纠错,以保证数据的完整和一致性。所谓纠错,实际上就是预先指定一些规则,字段和记录的值必须符合这些规则。
大多数符合SQL-92的RDBMS都支持下列纠错:
.NOT NULL字段必须有值;
.NOT NULL UNIQUE字段必须有值而且不能与其他记录重复;
.CHECK字段的值必须在一个范围内;
.CONSTRAINT在表格级对字段的值进行检查;
.PRIMARY KEY指定一个或几个字段作为关键字段;
.FOREIGN KEY指定一个或几个字段引用其他表格。
当然,不是所有的数据库都支持上述纠错,也有的服务器还支持其他纠错。其实,许多数据库桌面系统也支持纠错,不过,在服务器端纠错的优势是,多个客户程序可以共享服务器端的纠错,而不必在每个客户程序中重复一些代码。
应用服务器可以借用远程数据库服务器的纠错规则,对客户程序传递过来的数据进行纠错,这就要用到Constraints属性,只要把这个属性设为True(默认)。
如果不想借用远程数据库服务器的纠错规则,应当把Constraints属性设为False。
3.6 创建客户程序的一般步骤
在多层体系结构中,一个客户程序至少要有一个TClientDataSet构件,它的作用是引入数据集。TClientDataSet是从TDataSet继承下来的,它不需要依赖BDE。
创建一个客户程序的一般步骤是:
第一步是使用“File”菜单上的“New Application”命令开始一个新的项目,然后使用“File”菜单上的“New”命令,再双击“Data Module”图标加入一个数据模块。
第二步是把一个或几个MIDAS连接构件如TDCOMConnection、TSocketConnection、TOLEnterpriseConnection、TCorbaConnection、TRemoteServer或TMIDASConnection加到数据模块上。至于究竟选择哪一种MIDAS连接构件,这取决于通讯协议。
第三步是设置有关属性指定和连接应用服务器,这与具体的MIDAS连接构件有关。有的MIDAS连接构件还有ObjectBroker属性,可以指定一个TSimpleObjectBroker构件,这样就可以动态地选择应用服务器。
第四步是把一个或几个TClientDataSet构件放到数据模块上,设置RemoteServer属性指定一个MIDAS连接构件,设置ProviderName属性指定应用服务器上的TDataSetResolver 或TProvider构件,这样,客户程序就可以通过IProvider接口与应用服务器通讯。
第五步是把一个TDataSource构件放到数据模块上,设置它的DataSet属性指定TClientDataSet构件,再把一个数据控件如TDBGrid放到窗体上,设置它的DataSource属性指定TDataSource构件。至此,一个简单的客户程序创建完毕。
3.7 与应用服务器连接
要建立与应用服务器的连接,客户程序必须使用一个或几个MIDAS连接构件,这些构件可以在构件选项板的“MIDAS”页上找到。
不同类型的MIDAS连接构件使用不同的通讯协议,定位应用服务器的方式也不同。下面就详细介绍这几种MIDAS连接构件。
3.7.1 用DCOM来连接
要使用DCOM方式来连接应用服务器,就要用到TDCOMConnection构件。
TDCOMConnection构件的ComputerName属性用于指定应用服务器所在的计算机。如果ComputerName属性为空,TDCOMConnection就假设应用服务器与客户程序在同一个计算机上,或者应用服务器在系统注册表中可以找到。反过来说,如果应用服务器没有在客户端注册,而且与客户程序不在同一个计算机上,就须设置ComputerName属性。
即使应用服务器在客户端注册了,仍然可以设置ComputerName属性重新指定一个另一台计算机上的应用服务器。
ComputerName属性一般设为计算机的主机名或IP地址,如果指定的主机名或IP地址是非法的或者没有找到,TDCOMConnection就会触发异常。
如果客户程序需要在运行期间动态地选择应用服务器,最好不要用ComputerName属性来切换,而要加入一个TSimpleObjectBroker构件,再由ObjectBroker属性指定这个TSimpleObjectBroker构件。
3.7.2 用TCP/IP连接
要使用TCP/IP 方式来连接应用服务器,就需用到TSocketConnection构件。
TCP/IP 方式是一种应用广泛的连接方式,因为支持TCP/IP环境是相当普遍的。
TSocketConnection用Address属性或Host属性来定位应用服务器所在的计算机,前者用于指定IP地址,后者用于指定主机名,这两个属性是互斥的,只需要设置其中一个。此外,还要设置Port属性指定端口号,必须与应用服务器上运行的Scktsrver.exe或Scktsrvc.exe所使用的端口号一致,默认值是211。
3.7.3 用OLEnterprise连接
要使用OLEnterprise方式连接应用服务器,就要用到TOLEnterpriseConnection构件。
如果要直接连接应用服务器,不通过Business Object Broker,就要设置ComputerName属性指定应用服务器所在的主机名,就好像DCOM方式一样。
如果要通过Business Object Broker连接应用服务器,就要设置BrokerName属性指定Business Object Broker的名称。
ComputerName属性和BrokerName属性是互斥的,设置了其中一个,另一个就为清空。
3.7.4 用CORBA连接
要使用CORBA方式连接应用服务器,就须用到TCorbaConnection构件。
对于CORBA方式来说,只需要设置RepositoryID属性标识CORBA数据模块的接口,因为局域网中的智能代理(Smart Agent)会自动定位一个可用的应用服务器。
不过,如果不想让Smart Agent自动定位一个应用服务器,而是想指定一个特定的应用服务器,就要设置HostName属性指定应用服务器的主机名或IP地址。
TCorbaConnection构件可以通过两种方式获得应用服务器上CORBA数据模块的接口:
如果要使用静态联编方式,必须把_TLB.pas文件(由类型库编辑器生成)加到客户程序中。静态联编方式不仅能够在编译期进行类型检查,而且运行速度较快。
如果要使用动态联编方式,CORBA数据模块的接口必须用InterfaceRepository注册。
3.7.5 标识服务器
前面讲的是怎样定位应用服务器所在的计算机,现在要讲定位了计算机后,怎样标识应用服务器本身。
如果使用DCOM、TCP/IP或OLEnterprise方式来连接应用服务器,您可以通过ServerName属性或ServerGUID属性来标识应用服务器。其中,ServerName用于指定应用服务器,实际上就是一个已注册的OLE自动化对象名。ServerGUID属性用于指定远程数据模块的全局识别号。如果合法设置了ServerName属性,ServerGUID属性会自动被设置。
如果使用CORBA方式来连接应用服务器,就要用RepositoryID属性来标识应用服务器上CORBA数据模块的接口。
3.7.6 TSimpleObjectBroker
如果客户程序需要在运行期间动态地选择应用服务器,最好用TSimpleObjectBroker构件来切换,而不使用ComputerName属性。TSimpleObjectBroker能够自动维护一个可用的应用服务器的列表。当MIDAS连接构件需要连接一个应用服务器时,它就向TSimpleObjectBroker提出申请,TSimpleObjectBroker一般会随机提供一个应用服务器。如果TSimpleObjectBroker提供的应用服务器没法工作,TSimpleObjectBroker会再换一个,一直到MIDAS连接构件与应用服务器建立了连接为止。
一旦MIDAS连接构件与应用服务器建立了连接,它会自动把应用服务器的有关情况保存到有关属性中,如ComputerName、Address或Host等,因为MIDAS连接构件有可能会断开连接,以后又要再次连接,这时候就不必再向TSimpleObjectBroker提出申请。
在使用OLEnterprise或CORBA方式的情况下,不要用TSimpleObjectBroker构件,因为这两种方式都有自己专门的中介服务。
3.7.7 开始连接
进行了上述有关设置后,现在就可以连接应用服务器了。不过,在连接应用服务器之前,最好还要设置TClientDataSet的RemoteServer属性指定一个MIDAS连接构件,再设置ProviderName属性指定应用服务器中的TDataSetResolver 或TProvider构件。
当客户程序试图访问IProvider接口时,就会自动建立与应用服务器的连接,例如,把TClientDataSet的Active属性设为True。
当然,也可以通过MIDAS连接构件的Connected属性来连接或断开连接。
在将要与应用服务器建立连接之前,会触发BeforeConnect事件。当建立了与应用服务器的连接后,会触发AfterConnect事件。
3.7.8 断开连接
当进行下列操作时会使连接断开:
.把Connected属性设为False。
.关闭客户程序或MIDAS连接构件被删除。
.MIDAS连接构件的ServerName、ServerGUID、ComputerName、Host、Address等属性修改后,也会使原有的连接断开,再与基于新的应用服务器重新建立连接。
注意:尽量不要使用修改ComputerName、Host等属性的方式来切换应用服务器,最好用TSimpleObjectBroker,或者使用几个MIDAS连接构件分别建立连接。
与应用服务器的连接将要断开之前会触发BeforeDisconnect事件,连接真正断开之后,会触发AfterDisconnect事件。
3.8 调用服务器上的接口
通过TClientDataSet的Provider属性可以获得IProvider接口。其实,大部分情况下并不需要直接调用IProvider接口,因为对IProvider接口的调用已经封装在TClientDataSet的属性和方法中,唯一的例外是,DataRequest函数只能通过IProvider接口调用。
通过MIDAS连接构件的AppServer属性可以获得应用服务器上远程数据模块的接口,通过此接口可以调用远程数据模块的方法,例如:
MyConnection.AppServer.SpecialMethod(x,y);
这种调用方式是动态联编的,也就是说,编译器并不检查SpecialMethod的参数,甚至连有没有SpecialMethod它都不管。由于编译器不进行类型检查,在运行期实际调用时有可能失败。而且,动态联编没有静态联编的执行速度快。
如果用DCOM或CORBA作为通讯协议连接应用服务器,最好采用静态联编方式来访问远程数据模块的接口,方法就是用特定的接口类型对AppServer属性进行强制类型转换。假设远程数据模块的接口叫IMyAppServer(它的上级是IDataBroker),程序示例如下:
With MyConnection.AppServer as IMyAppServer Do SpecialMethod(x,y);
上面这行代码适合于DCOM方式,下面这行代码适合于CORBA方式:
With IUnknown(MyConnection.AppServer) as IMyAppServer Do SpecialMethod(x,y);
对于DCOM方式来说,要使用静态联编方式调用远程数据模块的接口,它的类型库必须在客户端注册。要注册类型库,可以调用匘ELPHI4BIN目录中的TREGSVR.EXE。
对于CORBA方式来说,要使用静态联编方式调用远程数据模块的接口,必须在客户程序中引用类型库编辑器生成的_TLB.pas文件。
对于TCP/IP或OLEnterprise方式来说,没法使用真正的静态联编方式来调用远程数据模块的接口,不过,可以通过远程数据模块的调度接口来改善性能,程序示例如下:
varTempInterface: IMyAppServerDisp;
Begin
TempInterface := MyConnection.AppServer;
...
TempInterface.SpecialMethod(x,y);
...
End;
要通过调用接口访问远程数据模块,必须在客户程序中引用类型库编辑器生成的_TLB.pas文件。
3.9 在客户端纠错
SQL Explorer可以把服务器端的纠错和默认表达式引入到一个数据字典中,这样,通过应用服务器上的基于BDE的数据集,客户端就可以借用服务器端的规则对数据纠错,这就是所谓的客户端纠错。如果用户在客户端输入或修改的数据违反了规则,这些数据就不会传递到应用服务器端,更不会传递到RDBMS端。
由此可见,在客户端纠错可以避免把错误的数据传递给远程数据库服务器,从而节省了网络上不必要的传输,因为这些数据即使传递给远程数据库服务器,也会被退回。
不过,有时候也需要暂时禁止在客户端纠错。例如,假设有一项纠错规则需要基于字段的最大值,而客户端一次只能检索若干条记录,也就是说,客户端统计出来的最大值与服务器端统计出来的最大值有可能不一样。如果客户端使用了过滤,也有可能使客户端统计出来的最大值与服务器端统计出来的最大值不一样。上述情况下,就需要暂时禁止纠错。
要暂时禁止纠错,可以调用TClientDataSet的DisableConstraints,DisableConstraints实际上是使一个内部的引用计数加1,当这个引用计数大于0,就不允许纠错。
要重新允许纠错,可以调用TClientDataSet的EnableConstraints,EnableConstraints实际上是使一个内部的计数减1,当这个计数减到0时,就可以重新允许纠错。
3.10 更 新 数 据
当客户程序从应用服务器检索到数据,就在内存中建立这些数据的副本。用户对数据进行编辑修改后,TClientDataSet专门用一个Delta属性存储变化了记录,包括更新、删除和插入的记录。为了使修改了的数据永久化,就要向数据库申请更新数据。
3.10.1 更新数据的一般步骤
首先,客户程序要调用ApplyUpdates函数向应用服务器提出申请,ApplyUpdates函数将通过IProvider接口把Delta属性传递给应用服务器。
应用服务器收到客户程序的申请后,再向远程数据库服务器提出申请,并且把被远程数据库服务器认为出错的记录暂时缓存起来。应用服务器上的TDataSetProvider或TProvider构件把出错的记录返回给客户程序,其中包括错误信息和错误代码。
客户程序收到这些出错的记录后,可以进行核对和修改,然后继续更新。注意:如果应用服务器端使用MTS类型的远程数据模块,就无法提供IProvider接口,这种情况下,必须通过远程数据模块的接口直接申请更新数据。
3.10.2 ApplyUpdates函数
当用户修改了数据后,应当调用ApplyUpdates函数向应用服务器申请更新数据。
ApplyUpdates函数只有一个MaxErrors参数,用于指定一个最大错误数,如果出错的记录数超过了这个参数的值,此次更新就停止。如果MaxErrors参数设为0,只要应用服务器发现有一个错误的记录,更新操作就停止。如果MaxErrors参数设为-1,当应用服务器发现有错误的记录,就尝试更新下一个记录,等所有的记录都尝试过以后才返回。
ApplyUpdates函数将返回实际遇到的错误数,同时,应用服务器将返回那些有错误的记录。
3.10.3 核对出错的记录
当应用服务器把出错的记录返回给客户程序时,将在客户端触发OnReconcileError事件,这样,您就有机会对记录进行核对并修改。OnReconcileError事件是这样声明的:
TReconcileErrorEvent = Procedure (DataSet: TClientDataSet; E: EReconcileError; UpdateKind:TUpdateKind; var Action: TReconcileAction) of object;
其中,DataSet参数是TClientDataSet构件名,籍此可访问数据集中某字段的NewValue、OldValue、CurValue等属性,从而分析有关出错的原因。通过DataSet参数也能够调用TClientDataSet的方法,但不能进行可能导致数据被修改的操作,否则,可能会引起死循环。
E参数是一个指向EReconcileError对象的指针,从中可以提取出有关错误信息和导致错误的原因。
UpdateKind参数表示是哪种操作导致了错误,可以是以下值:ukModify(修改)、ukInsert(插入)、ukDelete(删除)。
Action参数用于决定在退出处理OnReconcileError事件的句柄后是否继续更新数据集,可以设为下列值:raSkip、raAbort、raMerge、raCorrect、raCancel、raRefresh。下面这个程序示例演示了怎样调用RecError单元中的一个对话框:
Procedure TForm1.ClientDataSetReconcileError(DataSet:TClientDataSet;E:EReconcileError; UpdateKind:TUpdateKind, var Action TReconcileAction);
Begin
Action := HandleReconcileError(DataSet, UpdateKind, E);
End;
3.10.4 刷新记录
客户程序把数据在内存中建立一个副本,并工作于这个副本,而其他用户有可能已经修改了数据,也就是说,内存中的数据已不是数据库中的实际数据。
为了使内存中的数据是当前最新的,可以调用TClientDataSet的Refresh。不过,调用Refresh前要保证客户端没有未确定的修改,换句话说,就是客户端的日志中没有记载任何修改,否则会触发异常。
不过,TClientDataSet的RefreshRecord可以不管当前有没有未决的修改,它可以刷新当前记录,而日志中记载的修改继续保留。
注意:调用RefreshRecord有可能带来冲突。因此,调用RefreshRecord之前,最好还是检查一下当前是否有未决的修改,如果有的话,就触发异常,程序示例如下:
If ClientDataSet1.UpdateStatus usUnModified then
Raise Exception.Create('You must apply updates before refreshing the current record.');
ClientDataSet1.RefreshRecord;
3.10.5 从应用服务器获取参数
下列两种情况下,客户程序需要从应用服务器获得参数:
.客户程序需要知道存储过程的输出参数。
.客户程序需要初始化TQuery或TStoredProc的输入参数。
要从应用服务器获得参数,调用TClientDataSet的FetchParams函数,这个函数将返回有关参数并作为Params属性的值。在设计期也可以获取参数,办法是:用鼠标右键单击TClientDataSet构件,在弹出的菜单中选择“Fetch Params”命令。
注意:无论是在运行期调用FetchParams函数,还是在设计期使用“FetchParams”命令,客户端必须已经与应用服务器连接并且获得了IProvider接口,因为这些参数是由应用服务器上的TDataSetProvider或TProvider构件提供的,它们的DataSet属性必须指定了TQuery构件或TStoredProc构件。
也可以在客户端设置Params属性,然后把参数传递给应用服务器。
3.11 自定义应用服务器
MIDAS的结构是非常灵活的,可以自定义应用服务器以适应实际的需要。您可以从两个方面来自定义应用服务器,一是扩展远程数据模块的接口,二是使用自定义的数据集。
远程数据模块是应用服务器的核心部件,它提供了应用服务器与客户程序通讯的基本接口,除非客户程序直接调用IProvider接口。
如果应用服务器使用MTS类型的远程数据模块,客户程序只能通过远程数据模块的接口与应用服务器进行通讯。如果试图绕过MTS代理,就会使事务无效,特别是,由于MTS类型的远程数据模块可以与状态无关,这时候如果绕过MTS代理,有可能导致程序崩溃。
同样,单实例模式的CORBA数据模块也是与状态无关的,也存在着上述的问题。
为了使客户程序能够方便地访问MTS或CORBA数据模块(因为这时候没有IProvider接口),您必须对远程数据模块的接口进行扩展,添加一些方法让客户程序调用。
MTS或CORBA数据模块的接口都是从IDataBroker继承下来的。要向接口中加入新的属性或方法,您可以有两种操作方式:
一是使用“Edit”菜单上的“Add to Interface”命令,弹出“Add to Interface”对话框,如图3.6所示。
图3.6 “Add to Interface”对话框
在“Declaration”框内键入属性或方法的声明,单击OK按钮,Delphi 4就会把属性或方法加入到远程数据模块的接口中。
二是使用“View”菜单上的“Type Library”命令打开类型库编辑器,如图3.7所示。
在类型库编辑器中选择接口节点,例如图3.7中的IXXH,然后单击工具栏 上的“New Method”按钮或“New Property”按钮。加入了一个新的成员后,要设置有关属性。要说明的是,对于CORBA类型的远程数据模块的接口来说,许多属性是无效的。
对于基于COM的远程数据模块(TRemoteDataModule或TMTSDataModule)来说,新的成员将出现在接口的实现单元和类型库的描述文件中。
对于基于CORBA的远程数据模块(TCorbaDataModule)来说,新的成员除了加到接口的实现单元中外,还会自动生成一个_TLB单元。如果客户程序需要访问基于CORBA远程数据模块,必须引用这个单元。另外,您可以让类型库编辑器生成IDL脚本,然后用Interface Repository和Object Activation Daemon来注册接口。
客户程序可以通过MIDAS连接构件的AppServer属性获取远程数据模块的接口,然后通过接口调用远程数据模块的方法。
注意:如果使用MTS的话,每一个加入到接口中的方法必须在最后调用SetComplete,告诉MTS事务可以结束了。例如,下面的GetCustomerRecords函数用于获取记录:
Function TMyRemoteDataModule.GetCustomerRecords(MetaData: Boolean; outRecsOut: Integer):OleVariant;
Begin
Try
If MetaData then Result := CustomerProvider.GetRecords(0, RecsOut);
Else Result := CustomerProvider.GetRecords(-1, RecsOut);
SetComplete;
ExceptSetAbort;
End;
End;
在客户端,可以这样调用GetCustomerRecords函数:
ClientDataSet1.Data := CorbaConnection1.AppServer.GetCustomerRecords(False, RecsOut);
再举个例子,下面的ApplyCustomerUpdates函数用于申请更新数据:
Function TMyRemoteDataModule.ApplyCustomerUpdates(Delta: OleVarant; MaxErrors: Integer; outErrorCount: Integer); OleVariant;
Begin
Try
Result := CustomerProvider.ApplyUpdates(Delta, MaxErrors, ErrorCount);
SetComplete;
ExceptSetAbort;
End;
End;
在客户端,可以这样调用ApplyCustomerUpdates函数:
With ClientDataSet1 Do
Begin
CheckBrowseMode;
If ChangeCount 0 then
Reconcile(MyConnectionComponent.AppServer.ApplyCustomerUpdates(Delta,MaxErrors, ErrCount));
End;
在应用服务器上,一般要使用基于BDE的数据集构件来引入数据,TDataSetProvider或TProvider构件的DataSet属性指定此数据集构件。不过,默认情况下,TDataSetProvider或TProvider构件能够用动态生成的SQL语句直接与远程的数据库服务器通讯,而不需要借助于基于BDE的数据集构件,这样做的好处是减少了一个环节。
不过,TDataSetProvider或TProvider构件有时候也需要直接向应用服务器上的数据集构件申请更新数据,因为应用服务器上使用的有可能不是基于BDE的数据集,而是TClientDataSet或自定义的数据集。这时候需要把ResolveToDataSet属性设为True。
如果能确定应用服务器不需要用到BDE,最好用TDataSetProvider构件而不是TProvider构件,TDataSetProvider不需要依赖BDE, 有利于发布和安装应用服务器。
3.12 多层体系结构下的事务
当客户程序向应用服务器申请更新数据,TDataSetProvider或TProvider构件会自动把更新数据的例程加上一层事务的外套,换句话说,就是处于事务的控制之下。如果出错的记录数没有超过MaxErrors参数,就向远程数据库服务器正式提交此次事务,否则就滚回。
为了更好地支持事务,可以在应用服务器端用TDatabase构件来管理数据库的连接和事务,它的用法与两层体系结构一样。
如果在应用服务器端使用MTS,就可以获得更强大的事务处理能力。MTS事务可以延伸到所有的商业逻辑,而不局限于数据库访问。
另外,MTS的“两阶段提交”技术,使MTS能够跨多个数据库处理事务。要说明的是,“两阶段提交”技术目前只在Oracle和MS-SQL Server中完全实现,如果要跨数据库进行事务,而其中有的数据库不是Oracle和MS-SQL Server,就有可能出错。
正如前面提到的那样,如果要使用MTS类型的远程数据模块,应当扩展它的接口,用自定义的方法来封装MTS的事务功能。
3.13 把客户程序设计为ActiveForm
Delphi 4可以把分布式的数据库结构引申到Internet/Intranet上,把客户程序作为ActiveForm嵌入到网页中让人们下载,然后在当地执行。
Internet/Intranet上的应用服务器必须支持DCOM或TCP/IP连接方式,同样,设计成ActiveForm的客户程序也必须支持DCOM或TCP/IP连接方式,因为下载ActiveForm的计算机上可能没有安装OLEnterprise或CORBA运行期软件。
在设计客户程序的界面时,要用ActiveForm代替一般的窗体。为此,首先要使用“File”菜单上的“New”命令打开“New Items”对话框,选取“ActiveX”页,双击“ActiveForm”图标,打开ActiveForm向导,如图3.8所示。
单击“OK”按钮,Delphi 4就创建一个ActiveX项目,这个项目中只有一个空白的ActiveForm,下面的步骤就象设计一般的“瘦”客户一样。
以ActiveForm的形式设计好一个“瘦”客户程序后,还需要把它发布到Web服务器上,供人们下载。为此,首先要使用“Project”菜单上的“Web DeploymentOptions”命令打开“Web Deployment Options”对话框,然后设置有关Web发布的选项,主要是指定ActiveForm在Web服务器上的URL。最后,使用“Project”菜单上的“WebDeploy”命令把ActiveForm发布到Web服务器上。
为了测试这个Active窗体,可以用一个Web浏览器如IE下载嵌入了ActiveForm的网页。如果客户程序通过DCOM与应用服务器连接,Windows 95中需要安装支持DCOM的程序——DCOM95,而Windows NT 4.0则不需要。