Delphi:ClientDataset+TDataSetProvider的数据保存问题
生活随笔
收集整理的這篇文章主要介紹了
Delphi:ClientDataset+TDataSetProvider的数据保存问题
小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
看到一篇介紹ClientDataSet和TDataSetProvider,非常精彩,特此保存。
===========================================================================
TClientDataSet用法第十一章 TClientDataSet
與TTable、TQuery一樣,TClientDataSet也是從TDataSet繼承下來的,它通常用于多層體系結構的客戶端。TClientDataSet最大的特點是它不依賴于BDE(Borland Database Engine),但它需要一個動態鏈接庫的支持,這個動態鏈接庫叫DBCLIENT.DLL。在客戶端,也不需要用TDatabase構件,因為客戶端并不直接連接數據庫。
由于TClientDataSet是從TDataSet繼承下來的,所以,它支持諸如編輯、搜索、瀏覽、糾錯、過濾等功能。由于TClientDataSet在內存中建立了數據的本地副本,上述操作的執行速度很快。也正是由于TClientDataSet并不直接連接數據庫,因此,客戶程序必須提供獲取數據的機制。在Delphi 4中,TClientDataSet有三種途徑獲取數據:
.從文件中存取數據。
.從本地的另一個數據集中獲取數據。
.通過IProvider接口從遠程數據庫服務器獲取數據。
在一個客戶程序中,可以同時運用上述三種機制獲取數據。?
11.1 瀏覽和編輯數據
和其他數據集構件一樣,可以用標準的數據控件顯示由TClientDataSet引入的數據集,當然,這需要借助于TDataSource構件。
由于TClientDataSet是從TDataSet繼承下來的,所以,凡是其他數據集構件支持的功能,TClientDataSet構件也大致具備。不同的是,TClientDataSet能夠在內存中建立數據的副本,因此,TClientDataSet比其他數據集構件增加了一些特殊的功能。
11.1.1 瀏覽數據
可以用標準的數據控件顯示由TClientDataSet引入的數據集。在運行期,可以調用諸如First、GotoKey、Last、Next和Prior等函數來瀏覽數據。
TClientDataSet也支持書簽功能,可以用書簽來標記某條記錄,以后就可以方便地找到這條記錄。
對于TTable、TQuery等數據集構件來說,只能讀RecNo屬性來判斷當前記錄的序號。對于TClientDataSet構件來說,還可以寫RecNo屬性,使某一序號的記錄成為當前記錄。
11.1.2 CanModify屬性
TDataSet的CanModify屬性用于判斷數據集中的數據是否可以修改。CanModify屬性本身是只讀的,也就是說,數據是否能夠修改不取決于應用程序。
不過,TClientDataSet構件有其特殊性,因為TClientDataSet已經把數據在內存中建立了副本,因此,應用程序可以決定是否允許修改數據。如果不允許用戶修改數據,只要把ReadOnly屬性設為True,此時,CanModify屬性肯定返回False。
與其他數據集構件不同,修改TClientDataSet構件的ReadOnly屬性時,不需要事先把Active屬性設為True。
11.1.3 取消修改
TClientDataSet傳輸數據的基本單位稱為數據包,當前的數據包可以由Data屬性來訪問。不過,用戶對數據的修改并不直接反映到Data屬性中,而是臨時寫到一個日志即Delta屬性中,這樣做的好處是以后隨時可以取消修改。
不過,這里要說明一點,盡管用戶的修改并沒有反映到Data,當用戶在數據控件中看到的卻是最新修改的數據。如果一條記錄被反復修改了多次,用戶看到的只是最新的數據,但日志中卻記載了多次。
要取消上一次的修改,調用UndoLastChange函數。UndoLastChange需要傳遞一個布爾類型的參數叫FollowChange,如果FollowChange參數設為True,光標就移到被恢復的記錄上,如果FollowChange參數設為False,光標仍然在當前記錄上。
ChangeCount屬性返回日志中記載的修改次數。如果一條記錄被反復修改了多次,每調用一次UndoLastChange能夠逐級取消上一次的修改。
UndoLastChange只能取消上一次的修改,如果想一下子取消所有的修改,首先要選擇一個記錄,然后調用RevertRecord。RevertRecord將從日志中取消所有對當前記錄的修改。
TClientDataSet還有一個SavePoint屬性,它能把當前的編輯狀態保存起來,以后隨時可以返回當時的狀態。例如,可以這樣保存當前的狀態:
BeforeChanges := ClientDataSet1.SavePoint;
以后,可以這樣來恢復當時的狀態:
ClientDataSet1.SavePoint := BeforeChanges;
應用程序可以保存多處狀態,可以恢復其中一個狀態,不過,一旦某個狀態被恢復,在其之后的狀態就無效。
如果要一下子取消日志中記載的所有修改,可以調用CancelUpdates函數。CancelUpdates將把日志清空,取消所有的修改。
如果LogChanges屬性設為False,用戶對數據的修改就會直接反映到Data屬性中。?
11.1.4 合并修改
要把日志中記載的修改合并到Data屬性中,有兩種方式,具體使用哪一種方式,取決于應用程序獲取數據的機制。不過,不管是哪種機制,合并后,日志自動被清空。
對于一個從文件中獲取數據的程序來說,只要調用MergeChangeLog函數,就把日志中記載的修改合并到Data屬性中。不用擔心其他用戶同時修改了數據。
對于一個從應用服務器獲取數據的程序來說,就不能調用MergeChangeLog來合并數據,而要調用ApplyUpdates函數,ApplyUpdates會把日志中記載的修改傳遞給應用服務器,待應用服務器成功地把數據更新了數據庫服務器后,才會合并到Data屬性中。
11.1.5 糾錯
TClientDataSet支持糾錯功能。一般情況下,需要自己建立糾錯規則,以便對用戶輸入的數據進行糾錯。
此外,如果獲得了IProvider接口的話,還可以從遠程服務器引入糾錯規則。
有時候,客戶端可能需要暫時禁止糾錯,因為客戶端從應用服務器檢索數據是分階段進行的,在所有的數據檢索完畢之前,有些糾錯規則很可能會報錯。?
要暫時禁止糾錯,可以調用DisableConstraints,要重新允許糾錯,可以調用EnableConstraints函數。DisableConstraints和EnableConstraints實際上都是作用于一個內部的計數。
11.2 索 引
使用索引有這么幾個好處:
.在數據集中定位記錄比較快。
.能夠在兩個數據集之間建立Lookup或Master/Detail關系。
.可以對記錄排序。
在多層體系結構中,當客戶程序從應用服務器檢索數據時,它同時獲得了默認的索引。默認的索引叫DEFAULT_ORDER,可以使用這個索引排序,但不能修改或刪除這個索引。
除了默認的索引外,TClientDataSet還對日志中記載的記錄自動建立了一個副索引叫CHANGEINDEX。與DEFAULT_ORDER一樣,不能修改或刪除這個副索引。
另外,還可以使用數據集中已建立的其他索引,或者自己建立索引。
11.2.1 創建一個新的索引
要創建一個新的索引,可以調用AddIndex。AddIndex需要傳遞若干個參數:
一是Name參數,用于指定索引名。在運行期切換索引時需要用到索引的名稱。
二是Fields參數,它是一個字符串,用于指定索引中的字段名,彼此之間用分號隔開。
三是Options參數,用于設置索引的選項,包含ixDescending元素表示按降序排列,包含ixCaseInsensitive元素表示大小寫不敏感。
四是DescFields參數,它也是一個字符串,用于指定若干個字段名,這些字段將按照降序排列。
五是CaseInsFields參數,它的作用與DescFields參數類似,包含在CaseInsFields參數中的字段將對大小寫不敏感。
六是GroupingLevel參數,用于指定分組級別,其值不能超過索引中的字段數。
下面的代碼創建了一個索引:
If Edit1.Text <> /'/' and ClientDataSet1.Fields.FindField(Edit1.Text) then
Begin
ClientDataSet1.AddIndex(Edit1.Text+/'Index/',Edit1.Text,
[ixCaseInsensitive],/'/',/'/',0);
ClientDataSet1.IndexName := Edit1.Text + /'Index/';
End;
為了避免創建一個索引,可以臨時用IndexFieldNames屬性來指定若干個字段,讓數據集按這些字段排序。
11.2.2 刪除和切換索引
要刪除一個先前創建的索引,可以調用DeleteIndex并指定要刪除的索引名稱。注意:DEFAULT_ORDER和CHANGEINDEX不能刪除。
如果建立了多個索引,可以任意選擇其中的一個索引,這就要用到IndexName屬性。
11.2.3 用索引把數據分組
選擇了一個索引后,數據集將自動按其中的字段進行排序。這樣,臨近的記錄往往在關鍵字段上含有相同的值。例如,假設有一個表是這樣的:
SalesRep Customer OrderNo Amount
1 1 5 100
1 1 2 50
1 2 3 200
1 2 6 75
2 1 1 10
2 3 4 200
可以看出,SalesRep字段的值有重復的。對于SalesRep字段的值為1的來說,Customer字段的值也有重復的。這就是說,可以按SalesRep字段分組,進而再按Customer字段分組。顯然,這里的分組級別是不同的,按SalesRep字段建立的分組屬于第一級,按Customer字段建立的分組屬于第二級。實際上,分組級別取決于字段在索引中的順序。
TClientDataSet可以決定是否按照分組級別來顯示記錄的值。例如,也許想以下面這種形式顯示數據:
SalesRep Customer OrderNo Amount
1 1 5 100
2 50
2 3 200
6 75
2 1 1 10
2 3 4 200
要判斷當前記錄某一級的什么位置,可以調用GetGroupState函數。GetGroupState函數需要傳遞一個參數,用于指定分組級別。
11.3 計 算 字 段
與其他數據集一樣,也可以在TClientDataSet建立的數據集中增加計算字段。計算字段的值是基于同一個記錄中的其他字段計算出來的。
在其他數據集中,只要用戶修改了數據或當前記錄發生改變,就會觸發OnCalcFields事件,換句話說,計算字段的值就被計算一次。
TClientDataSet引入了“內部計算字段”的概念。與一般的計算字段不同的是,內部計算字段的值將隨其他字段的值一起存取,這樣,只有當用戶修改了數據才會觸發OnCalcFields事件,如果僅僅改變了當前記錄,不會觸發OnCalcFields事件。也就是說,內部計算字段的值需要重新計算的機會大大減少。
在處理OnCalcFields事件的句柄中,首先要判斷State屬性。如果State屬性返回dsInternalCalc,此時需要計算內部計算字段的值。如果State屬性返回dsCalcFields,此時需要計算一般的計算字段的值。
11.4 統 計 值
TClientDataSet增加了統計的功能,它可以基于分組自動計算總和、平均、計數、最大、最小值。當用戶編輯數據時,這些統計值會自動跟著變化。
11.4.1 指定統計方式
要指定怎樣進行統計,就要用到Aggregates屬性。這個屬性是一個TAggregates對象,它用于管理一組TAggregate對象。
在設計期,可以單擊Aggregates屬性邊上的省略號按鈕打開如圖11.1所示
的編輯器。
圖11.1 管理一組TAggregate對象
單擊按鈕可以增加一個TAggregate對象,單擊按鈕可以刪減一個TAggregate對象,單擊按鈕可以把TAggregate對象前移,單擊按鈕可以把TAggregate對象后移。
可以用字段編輯器專門創建一個用于表達統計值的字段,該字段的類型必須是“Aggregate”。Delphi 4會自動創建一個TAggregate對象,并加到Aggregates屬性中。選擇一個TAggregate對象,Object Inpector將顯示該對象的屬性。
其中,Expression屬性用于指定統計表達式,例如:
Sum(Field1)
也可以是比較復雜的表達式:
Sum(Qty * Price) - Sum(AmountPaid)
在表達式中,可以使用下列統計運算符:
.Sum計算一組數據的總和。
.Avg計算一組數據的平均值。
.Count計算一組數據中的非空值的個數。
.Min計算一組數據的最小值。
.Max計算一組數據的最大值。
除了上述幾個統計運算符外,還可以使用過濾條件中所能使用的運算符,但不能嵌套。在一個表達式中,可以混合出現幾個統計值或常量,但不能混合出現統計值和字段。
Sum(Qty * Price){合法}
Max(Field1) - Max(Field2){合法}
Avg(DiscountRate) * 100{合法}
Min(Sum(Field1)){非法,不能嵌套}
Count(Field1) - Field2{非法,統計值和字段不能混合出現在一個表達式中}
11.4.2 指定分組
默認情況下,統計值是基于數據集中所有的記錄計算出來的。不過,也可以針對一部分記錄計算統計值,這就需要事先建立分組。
前面在介紹索引時已經提到分組的概念。可以通過IndexName屬性和GroupingLevel屬性來選擇使用哪個索引以及最大的分組級別。
例如,假設有一個表是這樣的:
SalesRep Customer OrderNo Amount
1 1 5 100
1 1 2 50
1 2 3 200
1 2 6 75
2 1 1 10
2 3 4 200
如果要按SalesRep字段分組,并且指定其中的第一級,程序代碼應當這樣寫:
Agg.Expression := /'Sum(Amount)/';
Agg.IndexName := /'SalesCust/';
Agg.GroupingLevel := 1;
Agg.AggregateName := /'Total for Rep/';
11.4.3 怎樣獲取統計值
要獲取統計值,可以調用TAggregate對象的Value函數。如果統計值是基于數據集中所有的記錄計算出來的,隨時可以調用Value函數。如果統計值是基于分組計算出來的,必須保證當前記錄正好位于該分組內。因此,在調用Value之前,最好先調用GetGroupState函數看看當前記錄是否位于該分組內。
要在數據控件中顯示統計值,必須事先在字段編輯器中創建一個永久字段對象,該字段的類型必須是Aggregate。
11.5 數 據 包
通過Data屬性可以訪問客戶程序從應用服務器檢索到的數據。程序示例如下:
Procedure TForm1.Button1Click(Sender: TObject);
Begin
ClientDataSet1.Data := ClientDataSet1.Provider.DataRequest(FilterEdit.Text);
End;
11.5.1 直接對Data屬性賦值
前面講過,客戶程序既可以通過IProvider接口獲取數據,也可以從另一個數據集獲取數據,后者就是通過Data屬性賦值的。程序示例如下:
ClientDataSet1.Data := ClientDataSet2.Data;
一旦Data被賦值,就可以用標準的數據控件顯示這些數據。
注意:當從另一個數據集獲取數據時,另一個數據集的日志也將被復制過來,但不包括原來的范圍和過濾條件。
如果要從另一個基于BDE的數據集中獲取數據,可以通過數據集構件的Provider屬性,程序示例如下:
ClientDataSet1.Data := Table1.Provider.Data;
如果要從一個自定義的數據集獲取數據,首先要創建一個臨時的TProvider構件,然后設置其DataSet屬性指定這個自定義的數據集。程序示例如下:
TempProvider := TDataSetProvider.Create(Form1);
TempProvider.DataSet := SourceDataSet;
ClientDataSet1.Data := TempProvider.Data;
TempProvider.Free;
11.5.2 在數據包中加入自定義的信息
可以把自定義的信息加到數據包中。當把數據保存到文件或流中時,這些自定義的信息也將保存到文件或流中。如果把數據包直接賦值給另一個數據集的話,這些自定義的信息也將被復制。
要把自定義的信息加到數據包中,可以調用SetOptionalParam函數。要從數據包中檢索自定義的信息,可以調用GetOptionalParam。程序示例如下:
Procedure TAppServer.Provider1UpdateData(Sender: TObject; DataSet: TClientDataSet);?
var
WhenProvided: TDateTime;
Begin
WhenProvided := DataSet.GetOptionalParam(/'TimeProvided/');
...
End;
11.5.3 克隆另一個數據集
調用TClientDataSet的CloneCursor函數可以獲得一個數據集的完全相同的副本。它與直接通過Data屬性賦值是有區別的。
區別之一:數據在兩個數據集之間是共享的,修改其中一個將同時修改另一個。
區別之二:除了數據外,CloneCursor函數還復制了一些屬性和事件,這取決于Reset和KeepSettings參數怎樣設置。
CloneCursor函數需要傳遞三個參數,其中,Source參數指定源數據集,Reset參數和KeepSettings參數用于設置除了數據外是否還要復制下列屬性和事件:Filter、Filtered、FilterOptions、OnFilterRecord、IndexName、MasterSource、MasterFields、ReadOnly、RemoteServer、ProviderName、Provider。
如果Reset和KeepSettings參數都設為False,源數據集的上述屬性和事件都將被復制給目標數據集。如果Reset參數設為True,目標數據集的上述屬性和事件都將被清空。如果Reset參數設為False,而KeepSettings參數設為True,目標數據集的上述屬性和事件不變,不過,必須保證這些屬性和事件與克隆后的數據相容。
11.6 與應用服務器通訊
在多層體系結構中,客戶程序通過IProvider接口與應用服務器交換數據。這一章介紹怎樣在客戶端獲得IProvider接口、怎樣向應用服務器傳遞參數、怎樣向應用服務器請求數據、怎樣把用戶對數據的修改寫到數據庫中。
11.6.1 怎樣在客戶端獲得IProvider接口
在單層應用程序以及工作在“公文包”模式下的多層應用程序中,不需要用到IProvider接口。而在多層體系結構中,客戶程序要與應用服務器交換數據,首先必須獲得IProvider接口,這就要用到RemoteServer屬性和ProviderName屬性。
RemoteServer屬性用于指定客戶端的MIDAS連接構件。MIDAS連接構件又稱Data Broker,用于建立和維護與應用服務器的連接。
在設計期,正確設置了RemoteServer屬性后,就可以在對象觀察器中為ProviderName屬性選擇一個值,實際上就是選擇應用服務器上的一個TProvider構件。
11.6.2 向應用服務器傳遞參數
客戶程序可以向應用服務器傳遞參數,這些參數實際上是傳遞給應用服務器上的TQuery構件或TStoredProc構件。既可以在設計期也可以在運行期設置參數。
在設計期,可以單擊Params屬性邊上的省略號按鈕,打開一個如圖11.2所示的編輯器。
圖11.2 設置參數
單擊按鈕可以增加一個參數,單擊按鈕可以刪減一個參數,單擊按鈕可以把一個參數前移,單擊按鈕可以把一個參數后移。
選擇一個參數,對象觀察器將顯示該參數(TParam對象)的屬性。
在運行期可以調用TParams的CreateParam函數來創建一個參數。例如,下面的代碼創建了一個參數叫CustNo,它的使用類型是ptInput,數據類型是ftInteger,它的值設為605。
With ClientDataSet1.Params.CreateParam(ftInteger, /'CustNo/', ptInput) Do
AsInteger := 605;
設置好參數以后,如果TClientDataset的Active屬性是False,只要把Active屬性設為True,這些參數將被自動傳遞給應用服務器。如果Active屬性已經為True,就要調用SendParams函數把參數傳遞給應用服務器。
注意:傳遞給應用服務器的參數必須與TQuery構件或TStoredProc構件的參數匹配,包括名稱、數據類型和參數類型。
11.6.3 怎樣向應用服務器請求數據
TClientDataSet提供了兩個屬性和三個方法,用于怎樣向應用服務器請求數據:
一是FetchOnDemand屬性。如果這個屬性設為True,TClientDataSet會根據需要自動檢索附加的數據包,例如BLOB字段的值或者嵌套表的內容。如果這個屬性設為False,程序需要顯式地調用GetNextPacket才能獲得這些附加的數據包。
二是PacketRecords屬性,用于設置一個數據包中最多可容納的記錄數,設為-1表示一個數據包可以容納數據集的所有記錄。
三是GetNextPacket函數,用于向應用服務器檢索下一個數據包,并把檢索到的數據包添加到前一次檢索到的數據包的后面。這個函數返回實際檢索到的記錄數。
四是FetchBlobs過程,用于從應用服務器檢索BLOB字段的值。如果FetchOnDemand屬性設為True,就沒必要調用FetchBlobs函數。
五是FetchDetails過程,用于檢索嵌套表中的數據。如果FetchOnDemand屬性設為True,就沒必要調用FetchDetails函數。
11.6.4 更新數據庫
在多層體系結構中,用戶在客戶端修改了數據后,需要把最新的數據寫到數據庫中,這就要調用TClientDataSet的ApplyUpdates函數。
ApplyUpdates只需要傳遞一個參數叫MaxErrors,用于指定一個整數,當遇到無法更新的記錄超過這個數時,此次更新就中止。如果MaxErrors參數設為0,表示只要遇到一個錯誤更新就中止,客戶端的日志保持不變。如果MaxErrors參數設為-1,當應用服務器發現有錯誤的記錄,就嘗試更新下一個記錄,等所有的記錄都嘗試過以后才返回。
ApplyUpdates會自動調用Reconcile函數,進而調用應用服務器上的TProvider構件的ApplyUpdates函數去更新遠程的數據庫服務器。沒有被DBMS服務器認可的記錄通過Reconcile返回給客戶端,此時將在客戶端觸發OnReconcileError事件讓您更正錯誤。最后,ApplyUpdates函數返回仍然沒有被認可的記錄數。
11.7 在文件中存取數據
要從文件中讀取數據,可以調用LoadFromFile函數。LoadFromFile函數需要傳遞一個參數,用于指定文件名。文件名應包含完整的路徑。如果客戶程序總是從一個固定的文件中讀取數據,可以設置FileName屬性指定一個文件名,以后,當TClientDataSet引入的數據集打開時,就自動從這個文件中讀取數據,不需要調用LoadFromFile。
要從流中讀取數據,可以調用LoadFromStream。LoadFromStream需要傳遞一個參數,用于指定一個流對象。
注意:LoadFromFile(LoadFromStream)只能從先前用SaveToFile(SaveToStream)保存的文件中讀取數據。
要把數據保存到文件中,可以調用SaveToFile函數。SaveToFile需要傳遞一個參數,用于指定文件名。如果指定的文件已存在,文件中的數據將被覆蓋。如果客戶程序總是把數據保存到一個固定的文件中,可以設置FileName屬性指定一個文件名,當TClientDataSet引入的數據集關閉時,就自動把數據保存到這個文件中,不需要調用SaveToFile。
要把數據保存到流中,可以調用SaveToStream。SaveToStream需要傳遞一個參數,指定一個流對象。
注意:當把數據保存到文件或流中時,日志中記載的修改仍然保留。這樣,當下次調用LoadFromFile或LoadFromStream讀取數據時,仍然可以恢復原來的數據?
--------------------------------------------------------------------------------
?
-- ?作者:gzkhrh
-- ?發布時間:2005-7-29 8:42:46
-- ?
我們也跟著學學
==============
Delphi做為一個快速應用開發工具,深受程序員的喜愛。其強大的組件功能,讓程序員能夠輕松、高效地完成常見的界面開發、數據庫應用等功能。然而,幫助的相對缺乏,使得許多組件的功能并不為人們正確地使用,究其原因,仍然是認識上的問題。對于MIDAS開發中的核心部件,TClientDataSet和TDataSetProvider,由于資料的缺乏,人們在網上大多談論的是李維的書籍內容。我有幸在BDN上見到了Cary Jensen的Professional Developer系列文章,詳細闡述了DELPHI的數據庫開發技術。現節選出其中的ClientDataSet部分,與大家共同分享。?
ClientDataSet是一個功能強大的類,通過在內存中模擬表格,實現了其它數據集組件所不具備的強大功能。以往只在Delphi和C++ Builder企業版中才提供這個組件,如今,Borland的全部產品(包括最新的Kylix)都集成了TClientDataSet組件。?
TClientDataSet從類的繼承關系上來看,是TDataSet這個抽象類的子類,所以我們可以在TDataSet這個抽象層次上對其進行我們熟悉的操作,比如導航、排序、過濾、編輯。要注意的是,TClientDataSet使用了一種全新的技術,它將所有的數據均放在內存中,所以TClientDataSet是個只存在內存中的“虛擬表”,因此對數據庫的操作是非常快的。在PIII 850,512MB的機器上對十萬條記錄進行建索引的操作,花費的時間少于半分鐘。?
與一般的數據集組件不同,TClientDataSet使用的技術比較特別,本著高速度、低存儲需求的原則,TClientDataSet的內部使用了兩個數據存儲源。第一個是其Data屬性,這是當前內存數據的視圖,反映了所有的數據改變。如果用戶從數據中刪除一條記錄,則此記錄將從Data中消失,相應地,加入一條新記錄后,此記錄便存在Data屬性中了。?
另一個數據源是Delta屬性,故名思義,即增量的意思,這個屬性反映了對數據的改變。無論是向Data屬性新增還是刪除記錄,都會在Delta中記錄下來,如果是修改了Data中的記錄,則會在Delta保存兩條相應的記錄,一條是原始記錄,另一條僅包含修改的字段值。正因為Delta的存在和TClientDataSet在內存中記錄數據的特點,所有的改變都沒有立即更新加對應的物理存儲中,可以根據這些信息在適當的時候恢復,所以TClientDataSet天生具有緩沖更新功能。?
為了使數據更新回數據存儲源,我們要調用TClientDataSet中對應的方法。如果ClientDataSet與DataSetProvider關聯,那么僅需調用TClientDataSet的ApplyUpdates方法即可保存數據的更新,但如果TClientDataSet沒有對應的TDataSetProvider存在,而是直接同文件關聯,那么,這種方式是非常有趣的,我們在BriefCase模型中會再次講解這個問題。此時,如果使用TClientDataSet的SaveToFile和LoadFromFile,都會保留著Delta。調用MergeChangeLog和ClearChanges后,Delta的內容才會被?
清空。只是前者是將Delta的數據同Data結合起來,將改變存儲到物理介質上,而ClearChanges則是一股腦兒全部清空,將數據回復到原始狀態。大部分的應用都是將TClientDataSet與TDataSetProvider結合使用的。兩者聯合使用的行為反映了Borland的設計宗旨,就是要提供一個面向分布式環境的思路。我們下面來慢慢解釋。?
當我們將TClientDataSet對象的Active屬性設為True或者調用其Open方法后,ClientDataSet會向DataSetProvider發送一個取數據包請求。于是DataSetProvider便會打開對應的數據集,將記錄指針指向第一條記錄,然后從頭到尾依次掃描。對于掃描到的每一條記錄,都會將其編碼成一個variant數組,我們通常將它稱之為數據包。完成掃描后,DataSetProvider會關閉指向的數據集,并將所有的這些數據包傳遞給ClientDataSet。在我提供的演示程序中,你可以清楚地看到這種行為(畢竟眼見為實嗎!)。程序主界面右邊的DBGrid連接到一個指向數據庫表的數據源,DataSetProvider即指向此表。當選擇了ClientDataSet | Load菜單項時,你可以看到表格的數據被依次掃描,一旦到達最后一條記錄,表格便會被關閉,右邊的DBGrid被清空,而左邊反映ClientDataSet數據的DBGrid便出顯示出內存中的數據來。由于這個過程會在DBGrid上反映出來,所以不到1000條記錄的取出時間中,大部分都浪費在屏幕的更新顯示上了,你可以選擇ClientDataSet | View Table Loading來禁止顯示,而達到加速的目的。?
在上面的描述中,我們沒有提到一個重要的環節,即數據包是如何還原成表格的。那是因為DataSetProvider會將數據包中的元數據解碼出來,根據元數據(我們可以理解為數據表的結構)便可以構造出與物理數據表一模一樣的內存虛擬表。但要注意的是,盡管DataSetProvider指向的數據表可能有多個索引,但這些信息是不會放在數據包中的,換句話說,ClientDataSet當中的數據默認情況下是無索引的。但因為ClientDataSet具有與TDataSet一致的行為,所以我們可以在此基礎上根據需要重建索引。?
在ClientDataSet中的數據被修改后,可以提交給物理數據表持久化這此改變。這個工作便是由DataSetProvider完成的。內部工作原理是:DataSetProvider創建一個TSQLResolver的實例,這個實例會生成要在底層數據上執行更改的SQL語句。詳細地說,就是對修改日志中的每一條被刪除、插入、更改記錄生成對應的SQL語句。這個語句的生成也可以由用戶控制,DataSetProvider的UpdateMode屬性和ClientDataSet中的ProviderFlags屬性都對SQL語句的生成有影響。?
當然,你也可以換一種方式,即采取同單機或C/S結構一樣的數據直接操作機制,繞過SQL語句和緩沖更新機制來修改數據庫。只需將ResolveToDataSet屬性設為True,那么DataSetProvider在持久化更新時便不會使用TSQLResolve,而是直接修改物理數據源。即定位到要刪除的記錄,調用刪除語句,定位到修改記錄,調用修改語句。我們可以對演示程序稍加修改,觀察此種行為。請將演示程序中的DataSetProvider的ResolveToDataSet屬性由False改為True,運行。在界面中修改數據并且保存,你將會看到右邊的導航按鈕會在瞬間變得可用。?
更絕妙的是,Borland考慮到了應用的多樣性,為我們提供了BeforeUpdateRecord事件,這樣,當DataSetProvider對每個修改日志的記錄進行操作時,都會觸發此事件,我們可以在此事件中加入自己的處理,如“加密操作”、“商業敏感數據處理”等應用,從而極大地方便了程序員,讓程序員對于數據具有完全的控制能力。分布式環境的復雜性對數據的存取提出了更高的要求,所以使用事務來保證數據的完整性和一致性是非常必要的,Borland考慮到了這一點,當調用ClientDataSet的ApplyUpdates時,你可以傳遞一個整數值來指明可以容忍的錯誤數量。如果你的數據非常嚴格,則可以傳遞0值,這樣,DataSetProvider在應用修改時便會打開一個事務,如果遇到錯誤,便會回退此事務,修改日志將保持原樣,并且將出錯的記錄標記出來,最后會觸發OnReconcileError事件。如果傳遞了一個大于0的數,則當出現的錯誤數量小于此指定值時,事務會被提交,發生錯誤而導致提交失敗的記錄會保留在Delta中,而提交成功的記錄會從修改日志中刪除。若錯誤數量達到指定值,則事務會回退,結果同整數值為0的情況。如果值為負數,則會交所以可提交的數據都提交,不可提交的數據仍然保存在修改日志中,并將出錯記錄標記出來。?
雖然,Borland是為了滿足分布式編程的需要而設計了TClientDataSet,但在其它類型的編程環境中使用ClientDataSet也具有積極的意義。首先,我們可以看到,由于數據均在內存中進行操作,而且僅在打開數據庫取數據時和將修改持久到回數據庫時,才有數據庫開銷,其它時間數據庫為零,這樣就極大地增加了數據庫的負荷,讓數據庫服務器能滿足更多用戶的連接請求。其次,ClientDataSet具有其它數據集所不具備的許多高級功能,這為程序員進行復雜的編程提供了便利,可以不考慮數據庫本身是否支持這此功能,而讓ClientDataSet去處理這些復雜而繁瑣的細節。最后,ClientDataSet在數據存儲和應用程序間起到一個抽象層的作用。假如你的程序使用了TClientDataSet,那么如果你以后要更改數據庫存儲機制。比如說由BDE移植到dbExpress,或者從ADO移植到Interbase Express,你的用戶界面和數據控制部分幾乎就不用改變,只需要將DataSetProvider指向新的數據存取組件即可。順便說一句,由于緩沖更新的存在,用戶可能非常厭惡調用ApplyUpdates操作,那么你可以將此調用放入AfterPost和AfterDelte中,讓用戶的操作更方便。
第三章 創建多層應用程序
一個多層的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方式來說,要使用靜態聯編方式調用遠程數據模塊的接口,它的類型庫必須在客戶端注冊。要注冊類型庫,可以調用匼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則不需要。
--------------------------------------------------------------------------------
?
熟悉 MIDAS
你可能已經注意到了DELPHI中的縮寫MIDAS,然而你可能和多數DELPHI的開發者一樣,并未意識到這種技術帶來的巨大進步。Jani Jervinen 描述了MIDAS-Multi-tier Distributed Application Services Suite是如何讓你把CLIENTSERVER應用轉換成多層應用的。
? ? 傳統的CLIENTSERVER應用已經延續了很長時間,多數的DELPHI開發者通過編寫應用程序來提高水平。CLIENTSERVER技術對需要訪問遠端數據的簡單數據庫應用來說是個好的解決方案。
? ? 例如,DELPHI可以很方便的訪問ORACLE或MICROSOFT SQL SERVER 數據庫。然而,隨著應用的增大,維持它會消耗越來越多的資源。同時,隨著應用程序的用戶增加,性能會成為重要的問題。 ?
? ? 而且,INTERNET時代需要系統每天24小時不間斷運行。在CLIENTSERVER應用中,后端數據庫管理系統DBMS扮演著重要角色。如果服務器處理客戶端的請求失敗,會導致客戶端應用的失敗。
把多層變為視圖
? ? 為解決這個問題可以用多種解決方案。分布式就是其中一種,我確信你已經不止一次聽到過這個詞。然而,分布式應用的一個必須認真考慮的問題就是你必須對前面提到的問題非常熟悉。
? ? 雖然有很多方式來建立分布式應用,DELPHI的用戶仍然會對MIDAS技術感興趣。MIDAS已經成為DELPHI3及其后繼版本的一部分。如果你用的是DELPHI CLIENTSERVER的企業版,MIDAS的強大優勢仍然值得另外的花費。
? ? 通過MIDAS,你可以把CLIENTSERVER應用分發到層,每一層實現一種邏輯上獨立的功能。多層應用的優點就是你可以很方便的替換應用中的每一層,因為每一層都不知道其他層是如何實現它們的功能的。并且,每一層通過特殊的方式和其他層通訊,并只對其他層提供的服務感興趣,而不是如何提供這種服務。
最簡單的分布式應用是三層式應用。在三層式應用中,客戶端的角色就是顯示數據。中間層處理所有的交易邏輯,第三層是數據庫服務器。如圖1所示。
圖1
為什么多層?
? ? 多層應用的優勢在你比較傳統應用和多層應用的維護時會變得很明顯。當你不得不修改CLIENTSERVER應用時,你需要重建一個新的版本并重新部署它。如果有很多客戶,你可能需要整周的時間來做這件事。
? ? 例如,假設你在創建一個用于計算銷售人員工資的工資表的應用。工資依賴于銷售人員的銷售情況,突然,老板告訴你計算方法需要修改。
? ? 在CLIENTSERVER應用中,你需要修改客戶端的程序。然而,如果你已經把交易邏輯(計算方法)從用戶接口中分離出來,你就只需要替換中間層的應用。比較修改100個客戶端和只修改中間層的花費,不言而喻。
? ? 然而,節省開支并不是建立多層應用的唯一理由。當第一層只是用來顯示數據,建立INTERNET應用就會變得更經濟。例如,我的很多客戶問我如何創建他們最好的基于WEB的應用。我通常告訴他們解決這個問題的最好方法就是首先用MIDAS建立一個多層應用的版本,然后為它創建一個WEB用戶接口。當然,分布式應用對多層式來說解決起來并非難事,但是一旦你體會到了這項技術的優越性,你就會很快知道它什么時候有用或無用。
MIDAS 很容易創建
? ? 現在你已經知道了為什么多層式對傳統的CLIENTSERVER應用來說是種很好的選擇了,現在是更進一步學習MIDAS的時候了。第一個版本的MIDAS是在DELPHI 3 CLIENTSERVER中發布,所以在DELPHI 5 企業版中MIDAS已經是第三個版本了。雖然MIDAS最初是設計用于DELPHI,它同樣可以用于DELPHI的姊妹產品C++BUILDER。和DELPHI的其他應用開發一樣,MIDAS的開發就是把控件放到FORM上。所有的MIDAS控件可以在組件模板‘MIDAS’(見圖2)中找到。由于MIDAS是用來在層間傳送數據庫數據,多數控件跟共享和操作數據有關。表1對DELPHI 5 中所見到的控件給出了簡單的描述。
圖2
表 1. DELPHI 5 中的MIDAS 控件。
控件 描述
TClientDataSet (Enhanced TTable replacement to be used in MIDAS client applications)用于MIDAS的客戶端應用的增強的Ttable替代控件
TDCOMConnection (Connection component for using DCOM)用DCOM連接的連接控件
TSocketConnection (Lightweight connection component that uses TCPIP)用TCPIP連接的方便的連接控件。
TDataSetProvider (Component to "export" a dataset from a MIDAS application server)從MIDAS應用服務器‘輸出’數據集的控件
SimpleObjectBroker (Simple component to help in load-balancing MIDAS applications)用于MIDAS應用的負載平衡的簡單控件
TWebConnection (Component for tunneling database data through HTTP)通過HTTP訪問數據庫數據的控件
TCorbaConnection (Can be used to connect CORBA and MIDAS applications)可以用來連接CORBA和MIDAS應用
? ? 在MIDAS應用中,客戶端應用通過應用服務器來取數據,應用服務器依次從數據庫取數據。應用服務器把數據庫中的數據打包并返回給客戶端。在MIDAS術語中,應用服務器成為提供數據給客戶端。
? ? MIDAS 提供了三種不同的連接方式(CORBA 連接是特殊的并且通常不包含在內),一個應用服務器可以同時支持所有連接。應用服務器使用常規的數據庫控件來訪問數據庫,然后用一個數據provider控件來允許客戶端應用來訪問數據。客戶端應用使用連接控件來連接應用服務器。連接方式依賴于客戶端的應用—例如,簡單連接可以用TCPIP,然而復雜的客戶端應用就要用DCOM連接。
? ? 客戶端的應用在使用連接控件的同時使用Client Dataset控件,它的作用就象平常的Ttable一樣。例如,Client Dataset控件支持合計字段,過濾器,和主從關系,使你對MIDAS感到適應。
建立一個應用服務器
? ? 現在你可以用你的MIDAS知識來建立一個真正意義上的應用了。和所有的MIDAS應用一樣,最好從應用服務器端開始開發。首先,啟動DELPHI并選擇一個New Application。改變自動生成的FORM的大小以使它可以放一個Tlable控件,然后在它上面放一個Tlable控件,改變它的Caption屬性,比如改為‘My MIDAS Server’。 Label 和 form 的大小并不重要,因為它們只是用來在屏幕上指示MIDAS應用服務器。通常應用服務器根本不包含任何用戶接口,但為了學習,最好有個簡單的用戶接口。它只是讓你來控制服務器。
? ? 我在建立MIDAS應用服務器時最喜歡的一步就是設置窗口的背景顏色。改變主窗體的顏色對于確信應用服務器處于運行狀態來說是個很好的方法。當然,這只是在你創建服務器的用戶接口時才有用。
現在,選擇綠顏色,并設置label的字體顏色為白色,以增加對比度。然后從FILE菜單中選擇NEW,圖3顯示了NEW對話框中的選項,從中你要選擇的是 位于Multitier 頁面中的Remote Data Module圖標。我在此不想詳細描述,但是必須是一個Remote Data Module,因為MIDAS 是建立在COM基礎之上的。因此,你的Remote Data Module 會在后面的例子中自動說明。這里有三個不同的data modules 可用的原因就是MIDAS服務器可以用于不同的環境。例如,你的MIDAS應用可以和CORBA應用通訊。
圖3
現在回到代碼中,按New Items 對話框的OK按鈕后,你就會看到圖4所示的向導。因為remote data module是一個COM對象,你需要為它指定一個 CoClass 名稱。這個名稱在你的系統中必須是唯一的,所以我選擇‘MyGreenMIDASServer’為它的名稱。其他選項用它的缺省值,你需要參考表2來了解這些選項的用途。
圖4
表 2. Remote Data Module向導中的選項
選項 說明
CoClass Name (Specifies the name of the remote data module object.)標識remote data module對象的名字
Instancing (Controls how the remote module is created when the application server runs. For example, Single Instance means that every client connects to its own instance of the application server.)在應用服務器運行時控制remote module如何創建。例如,Single Instance表示每個客戶端用自己的實例連接到應用服務器
Threading model (Indicates how client calls are passed to the remote data module. For example, Free means that multiple threads are used.)說明客戶端的調用如何傳送到remote data module.例如,FREE表示使用多線程。
提供數據集
? ? 當remote data module建立好之后,你就可以給它增加普通的database components。我在例子中訪問的是DBDEMOS別名數據庫中的Biolife表,所以一個Ttable控件就夠了。
? ? MIDAS服務器可以支持所有從TdataSet類繼承而來的數據庫控件。事實上,這意味著你可以選擇用ADO,BDE,甚至InterBase Express來連接數據庫。你選擇的每種數據庫連接對客戶端應用來說都沒什么不同,所以當你決定用ADO替換BDE時客戶端不必做任何修改。這是不是很COOL?
? ? 為了訪問Biolife表,在data module上放一個Ttable控件,并把它和我們熟知的‘fish fact’表連接。為了確保它工作正常,可以activate Ttable 控件來測試數據庫的連接。然而,你隨后應該馬上關閉表,因為讓表保持OPEN狀態會耗盡寶貴的服務器資源。關閉表是安全的,因為MIDAS在需要時會自動激活控件。
然后,在表單上放一個TdataSetProvider控件(你可以在MIDAS控件頁面中找到這個控件)。你要做的就是設置dataset provider的DataSet屬性為Ttable 控件。做完這些以后,你就完成了應用服務器的建立,并可以保存整個project。我用RemoteDM,MainForm,和GreenServer來分別為remote data module,the green form,和project來命名。
? ? 當你保存了整個project以后,就可以馬上編譯并運行了。這是個重要的步驟。第一次運行應用服務器時會自動在Windows注冊表中注冊它。記住:事實上remote data modules是COM對象,所以注冊表中必須包含EXE文件的位置(目錄)信息,甚至更多。
客戶端應用
? ? 現在,你的應用服務器已經完成,可以建立客戶端的應用了。從View 菜單中選擇Project Manager選項,右擊project group 并選擇Add New Project。當熟悉的New Items對話框打開后,選擇Application圖標并選擇OK。一個新的表單會出現在IDE中。注意服務器端和客戶端應用要在同一個項目組中。這使得MIDAS的應用開發更容易,因為你可以很容易的在兩個項目中切換。另一種選擇就是打開兩個DELPHI,但我更喜歡第一種方法。
? ? 接下來,改變客戶端應用的表單大小使它可以放一個grid。從MIDAS控件頁面中選擇一個TDCOMConnection控件放到表單上,這個控件是你可以選擇的四種連接控件中的一個。如果你用的是Windows 95 或r 98,你需要在繼續前先閱讀補充說明‘Windows 95, 98 and DCOM’。
? ? 回到Object Inspector,設置連接控件的ServerName屬性為GreenServer.MyGreenMIDASServer。注意在Object Inspector中的列表中會自動顯示在本系統中注冊的應用服務器。當你選擇了服務器后,DELPHI會自動填寫ServerGUID屬性。你可以忽略GUID除非你對COM感興趣。
? ? 現在,你可以讓ComputerName屬性為空。這意味著客戶端在本機尋找應用服務器。如果你想在其他機器上運行服務器,可以指定ComputerName屬性值為其他機器名稱。
為了測試連接,置Connected屬性的值為True。然后你就會看到你的應用服務器開始在后臺運行(見圖5),這就是COM的奇妙之處。事實上,你已經看到了just-in-time activation的所有狀況。
圖5
? ? 接下來,用鼠標從從控件模板中選擇一個TclientDataSet控件放到表單上。設置它的RemoteServer屬性為connection控件的名字。選擇ProviderName屬性為應用服務器上的provider控件的名字。注意什么會發生:應用服務器會和你的客戶端應用通訊,并告訴它服務器提供什么數據集。
? ? 隨后的事就很熟悉了:放一個TdataSource和一個TDBGrid到表單上,把data source連接到client dataset,把grid連接到data source。現在,設置client dataset的Active屬性為True。如果順利的話,你就會在grid中看到Biolife表中的數據。這些數據沒有任何值得懷疑。
實施MIDADS應用
? ? 一旦你完成了MIDAS應用的創建,我確信就你會對實施它感興趣。雖然實施MIDAS應用會很EASY,你也要按它的基本步驟進行,它們可以一次完成。
? ? 在開始服務器應用之前,你需要兩個在SERVER上運行的DLL。這兩個DLL是STDVCL40.DLL 和 MIDAS.DLL。當安裝服務器時,你最好把這兩個文件拷貝到Windows系統目錄下,這樣你就可以很方便的找到它們。拷貝好之后,這兩個DLL還需要注冊。
? ? 注冊是個和COM有關的事情,并且是必須的,以使系統可以找到DLL。通常使用REGSVR32.EXE來注冊,它和Windows同時被安裝。然而,有些系統沒有這項功能,你就需要用TregSvr,它是DELPHI中的一個演示程序。你可以在DELPHI的Demos//ActiveX//TregSvr目錄下找到。
? ? 盡管這些工具用起來很方便,最好的方式還是用安裝工具生成一個安裝包。例如,我使用InstallShield(DELPHI專業版中提供)來創建一個安裝包,并設置MIDAS的DLLs為‘self-registering’。
給代碼授權
? ? 在你實施MIDAS應用之前,一定要注意MIDAS并非是免費軟件。當你實施你的MIDAS應用時,通常需要購買一個實施許可證。開發MIDAS應用程序不需要許可,因為,在DELPHI 5的企業版中已經有了。
? ? 在http://shop.borland.com/上的實施許可-$299.95允許你在一臺機器上實施你的MIDAS應用。這個許可對客戶端的連接數沒有限制,所以MIDAS事實上很便宜。
? ? 為了使MIDAS讓更多人買的起,Inprise公司對許可協議保留了一個例外。在DELPHI的DEPLOY.TXT文件中:"A server deployment license is not required… in an application in which the client and server reside on the same machine." (‘服務器的許可證不需要…當客戶端和服務趨端在同一臺機器上時’。)
更多MIDAS
? ? MIDAS讓你可以用你現在的DELPHI技術方便的開發分布式應用,而不必涉及DCOM 或 CORBA的更深內容。而且,你可以用DELPHI的開發方式建立你的應用程序,同時速度會很快。
? ? 盡管在一篇文章中并不能描述MIDAS的所有內容,我希望你可以在此學到最基本的技術。我建議對TclientDataSet控件花點時間,相信你不會對它的一些特性失望。
? ? 作為進一步的練習,我建議你測試不同的連接方式。例如,使用TsocketConnection,它可以讓你和服務器建立很lightweight的連接。同時,你可能想學習IappServer接口,它是MIDAS技術的基礎。
? ? 如果你想對MIDAS了解的更多,我建議你訪問Borland公司的WEB站點http://community.borland.com/。特別是Dan Miser寫的文章更值得一讀。同時要記住:將來不管遇到什么問題,也不會阻擋DELPHI開發者在MIDAS領域的旅程。
下載 JARVINEN.ZIP
補充說明: Windows 95, 98 and DCOM
? ? 如果你用的是Windows 95或98,你必須確保DCOM已經安裝。缺省情況下,Windows 95或98不包含DCOM,但Windows NT 和 Windows 2000已經安裝了。要在Windows 95上安裝DCOM,從DELHPI的安裝CD上運行DCOM95.EXE(這個文件位于安裝目錄下)。或者,瀏覽http://www.microsoft.com/com,并下載最新的安裝包。要在Windows 98上安裝,從相同的網址上下載最新的DCOM安裝版本。?
--------------------------------------------------------------------------------
其實也就一個主要問題,為什么保存數據時非得要先斷開連接,再重新連接后才能正常保存?
百思不得其解!
--------------------------------------------------------------------------------
在非多層的情況下,更新數據往往先判斷事務是否正在進行,多層不了解。不知是否有類似情況
procedure TForm1.ADODataSet1BeforePost(DataSet: TDataSet);
begin
if not ADOConnection1.InTransaction ?then
begin
ADOConnection1.BeginTrans;
end;
procedure TForm1.ADODataSet1AfterPost(DataSet: TDataSet);
begin
if ADOConnection1.InTransaction ?then
begin
ADOConnection1.CommitTrans;
end;
end;
--------------------------------------------------------------------------------
?
-- ?作者:HUWEIJI
-- ?發布時間:2005-8-3 19:38:33
-- ?
出錯的提示是:
Cannot create new transaction because capacty was exceeded.
可是除了BeforeUpdateRecord自動開啟的事務外,沒有任何其他事務也照樣出問題。
我用的是DBExpress,以前用ADO時也沒有問題。
保存的過程是 SqlConnection.Connection:=False;
? ? ? ? ? ? ? ? SqlConnection.Connction:=True;
?保存;
SqlConnection.Connection:= False;
少了一項都可能出問題。
如果只保存一個ClientDataSet中的數據是沒有問題的,當不止一個時,比如說父子表,就不知道怎么辦了。
去大富翁也查不出所以然來,真是郁悶 出處:?http://www.360doc.com/content/12/0817/17/7662927_230712619.shtml
轉載于:https://www.cnblogs.com/lrl45/p/5135450.html
總結
以上是生活随笔為你收集整理的Delphi:ClientDataset+TDataSetProvider的数据保存问题的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 什么是非贸易外汇 非贸易外汇的概述
- 下一篇: zoj 1670 Jewels from