注意: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則不需要。