三:创建多层应用程序(二)

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方式来说,要使用静态联编方式调用远程数据模块的接口,它的类型库必须在客户端注册。要注册类型库,可以调用匼DELPHI4/BIN目录中的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则不需要。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值