1. 與TTable、TQuery同樣,TClientDataSet也是從TDataSet繼承下來的,它一般用於多層體系結構的客戶端。不少數據庫應用程序都用了BDE,BDE每每給發佈帶來很大的不便,於是TClientDataSet最大的特色是它不依賴於BDE(Borland Database Engine),但它須要一個動態連接庫的支持,這個動態連接庫叫DBCLIENT.DLL。在客戶端,也不須要用TDatabase構件,由於客戶端並不直接鏈接數據庫。因爲TClientDataSet是從TDataSet繼承下來的,因此,它支持諸如編輯、搜索、瀏覽、糾錯、過濾等功能。因爲 TClientDataSet在內存中創建了數據的本地副本,上述操做的執行速度很快。也正是因爲TClientDataSet並不直接鏈接數據庫,所以,客戶程序必須提供獲取數據的機制。
在Delphi 4中,TClientDataSet有三種途徑獲取數據:
一、從文件中存取數據。
二、從本地的另外一個數據集中獲取數據。
三、經過IProvider接口從遠程數據庫服務器獲取數據。
在一個客戶程序中,能夠同時運用上述三種機制獲取數據。
和其餘數據集構件同樣,能夠用標準的數據控件顯示由TClientDataSet引入的數據集,固然,這須要藉助於TDataSource構件。因爲 TClientDataSet是從TDataSet繼承下來的,因此,凡是其餘數據集構件支持的功能,TClientDataSet構件也大體具有。不一樣的是,TClientDataSet可以在內存中創建數據的副本,所以,TClientDataSet比其餘數據集構件增長了一些特殊的功能。
在運行期,能夠調用諸如First、GotoKey、Last、Next和Prior等函數來瀏覽數據。TClientDataSet也支持書籤 (BookMark)功能,能夠用書籤來標記某條記錄,之後就能夠方便地找到這條記錄。對於TTable、TQuery等數據集構件來講,只能讀 RecNo屬性來判斷當前記錄的序號。對於TClientDataSet構件來講,還能夠寫RecNo屬性,使某一序號的記錄成爲當前記錄。
一、從文件中存取數據要從文件中讀取數據,能夠調用LoadFromFile函數。LoadFromFile函數須要傳遞一個參數,用於指定文件名。文件名應包含完整的路徑。若是客戶程序老是從一個固定的文件中讀取數據,能夠設置FileName屬性指定一個文件名,之後,當TClientDataSet 引入的數據集打開時,就自動從這個文件中讀取數據,不須要調用LoadFromFile。要從流中讀取數據,能夠調用LoadFromStream。 LoadFromStream須要傳遞一個參數,用於指定一個流對象。注意:LoadFromFile(LoadFromStream)只能從先前用 SaveToFile(SaveToStream)保存的文件中讀取數據。要把數據保存到文件中,能夠調用SaveToFile函數。 SaveToFile須要傳遞一個參數,用於指定文件名。若是指定的文件已存在,文件中的數據將被覆蓋。若是客戶程序老是把數據保存到一個固定的文件中,能夠設置FileName屬性指定一個文件名,當TClientDataSet引入的數據集關閉時,就自動把數據保存到這個文件中,不須要調用 SaveToFile。要把數據保存到流中,能夠調用SaveToStream。SaveToStream須要傳遞一個參數,指定一個流對象。注意:當把數據保存到文件或流中時,日誌中記載的修改仍然保留。這樣,當下次調用LoadFromFile或LoadFromStream讀取數據時,仍然能夠恢復原來的數據。
ClientDataSet強大的數據複製技術:
經過ClientDataSet.Data屬性能夠訪問客戶程序從應用服務器檢索到的數據。程序示例以下: html
也能夠直接賦值: sql
ClientDataSet1.Data:=ClientDataSet2.Data;//(至關於把ClientDataSet2的數據拷貝給ClientDataSet1,是否是很方便) 數據庫
從其餘數據集獲取數據(除ClientDataSet):express
3. 排序
ClientDataSet排序
一、簡單排序緩存
二、複雜排序(創建索引)
下面這個過程僅供參考(由於用到三方控件DBGridEh): 服務器
把上面的過程稍作修改,可用於標準DBGridvaride
4. 提交更新過程:
首先,客戶程序要調用ApplyUpdates函數嚮應用服務器提出申請,ApplyUpdates函數將經過IProvider接口把Delta(數據變更狀況)屬性傳遞給應用服務器。應用服務器收到客戶程序的申請後,再向遠程數據庫服務器提出申請,而且把被遠程數據庫服務器認爲出錯的記錄暫時緩存起來。應用服務器上的TDataSetProvider或TProvider構件把出錯的記錄返回給客戶程序,其中包括錯誤信息和錯誤代碼。客戶程序收到這些出錯的記錄後,能夠進行覈對和修改,而後繼續更新。注意:若是應用服務器端使用MTS類型的遠程數據模塊,就沒法提供IProvider接口,這種狀況下,必須經過遠程數據模塊的接口直接申請更新數據。
if ClientDataSet1.ChangeCount>0 then//有未決的修改
ClientDataSet1.ApplyUpdates(MaxErrors);//將修改提交到服務器
參數MaxErrors用於指定一個最大錯誤數,若是出錯的記錄數超過了這個參數的值,這次更新就中止。若是MaxErrors參數設爲0,只要應用服務器發現有一個錯誤的記錄,更新操做就中止。若是MaxErrors參數設爲-1,當應用服務器發現有錯誤的記錄,就嘗試更新下一個記錄,等全部的記錄都嘗試過之後才返回。ApplyUpdates函數將返回實際遇到的錯誤數,同時,應用服務器將返回那些有錯誤的記錄。
當應用服務器收到客戶的提交請求後,觸發OnUpdateData,這時就能夠對客戶提交的數據進行檢查和編輯:
如函數
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;
而後將編輯後的數據提交到數據庫服務器。
ClientDataSet1.CancelUpdates;//恢復全部修改過但未提交(包括提交未成功的)的記錄
ClientDataSet1.UndoLastChange;//恢復前一次的修改,至關於Undo功能
注意使用這種提交方式(ApplyUpdates)在查詢時儘量避免使用數據處理(關聯、分組、求和等等等),不然不能提交(除非本身寫一些特殊處理程序)post
procedure TForm1.DBGrid1TitleClick(Column: TColumn); begin if (not column.Field is Tblobfield) then//Tblobfield不能索引,二進制 ClientDataSet1.IndexFieldNames:=column.Field.FieldName; end;</span>
ClientDataSet2.Data:=ClientDataSet1.Data; 測試
ClientDataSet2.Open;
ClientDataSet2.CloneCursor(ClientDataSet1,true);
ClientDataSet2.Open;
小結:
ClientDataSet提供了好多種查找數據的方法。可是各自有其優缺點。
上面的例子中有Start;和Done,若是你有興趣,能夠加入計時點進行速度測試。
Scanning最簡單,可是最慢,由於比較慢,還得使用ClientDataSet.DisableControls和ClientDataSet.EnableControls方法(我在前面一片文章講過)。
Findkey/FindNearest(GotoKey/GotoNearest)代碼多,可是很是快。必須使用Index,不一樣的是Find須要的Index是必須創建好的,而Goto能夠在第一次使用時創建Index。
Locate使用最方便,不須要Index,可是速度沒有Find快
ClientDataSet使用心得和技巧 影響ClientDataSet處理速度的一個因素 TClientDataSet是Delphi開發數據庫時一個很是好的控件。有很強大的功能。 我經常用ClientDataSet作MemoryDataSet來使用。還能夠將ClientDataSet的數據保存爲XML,這樣就能夠作簡單的本地數據庫使用。還有不少功能就很少說了。在使用ClientDataSet的過程當中關於怎樣提升處理速度這個問題,我就我我的的一點點體會和你們分享一下。 一般狀況下咱們通常都是用 ...ClientDataSet-->DataSource-->DBComponent 這樣的結構,處理數據的時候就直接操做ClientDataSet。可是大多DBComponet都會當即響應ClientDataSet的變化。若是你是向ClientDataSet中插入不少數據時候,DBComponent就要響應幾回,並且響應過程根據不一樣的控件,速度,過程數量都不同。這樣就影響了程序的執行效率。因此在對ClientDataSet處理中,我是用ClientDataSet.DisableControls和ClientDataSet.EnableControls方法:打開和關閉DBComponent與ClientDataSet的數據顯示關係。 例如: ClientDataSet..DisableControls; ... for I := 0 to 10000 do begin ClientDataSet.Append; ... ClientDataSet.Post; end; ... ClientDataSet.EnableControls ... 這樣作之後你會發現處理速度比之前沒有使用方法的時候有成倍的提升。 ClientDataSet的數據查找。 我所介紹的心得和技巧都是用ClientDataSet來作範例,也能夠應用於其餘的一些DataSet。廢話就很少說了。咱們仍是先看代碼,讓後再總結。 1.Scanning 掃描數據查找 這是最簡單最直接也是最慢的一種方法,遍歷全部數據: procedure TForm1.ScanBtnClick(Sender: TObject); var Found: Boolean; begin Found := False; ClientDataSet1.DisableControls; Start; try ClientDataSet1.First; while not ClientDataSet1.Eof do begin if ClientDataSet1.Fields[FieldListComboBox.ItemIndex].value = SearchText then begin Found := True; Break; end; ClientDataSet1.Next; end; Done; finally ClientDataSet1.EnableControls; end; if Found then ShowMessage(SearchText + ' found at record ' + IntToStr(ClientDataSet1.RecNo)) else ShowMessage(ScanForEdit.Text + ' not found'); end; procedure TForm1.ScanBtnClick(Sender: TObject); var Found: Boolean; begin Found := False; ClientDataSet1.DisableControls; Start; try ClientDataSet1.First; while not ClientDataSet1.Eof do begin if ClientDataSet1.Fields[FieldListComboBox.ItemIndex].value = SearchText then begin Found := True; Break; end; ClientDataSet1.Next; end; Done; finally ClientDataSet1.EnableControls; end; if Found then ShowMessage(SearchText + ' found at record ' + IntToStr(ClientDataSet1.RecNo)) else ShowMessage(ScanForEdit.Text + ' not found'); end; 2.Finding 尋找數據 最老,可是最快的查找方式。 使用FindKey/FindNearest來查找一條或多條符合條件的數據,固然待查找的Field必須是一個IndexField。能夠看出,這種基於Index的查找速度是很是快的。 procedure TForm1.FindKeyBtnClick(Sender: TObject); begin Start; if ClientDataSet1.FindKey([SearchText]) then begin Done; StatusBar1.Panels[3].Text := SearchText + ' found at record ' + IntToStr(ClientDataSet1.RecNo); end else begin Done; StatusBar1.Panels[3].Text := SearchText + ' not found'; end; end; procedure TForm1.FindNearestBtnClick(Sender: TObject); begin Start; ClientDataSet1.FindNearest([SearchText]); Done; StatusBar1.Panels[3].Text := 'The nearest match to ' + SearchText + ' found at record ' + IntToStr(ClientDataSet1.RecNo); end procedure TForm1.FindKeyBtnClick(Sender: TObject); begin Start; if ClientDataSet1.FindKey([SearchText]) then begin Done; StatusBar1.Panels[3].Text := SearchText + ' found at record ' + IntToStr(ClientDataSet1.RecNo); end else begin Done; StatusBar1.Panels[3].Text := SearchText + ' not found'; end; end; procedure TForm1.FindNearestBtnClick(Sender: TObject); begin Start; ClientDataSet1.FindNearest([SearchText]); Done; StatusBar1.Panels[3].Text := 'The nearest match to ' + SearchText + ' found at record ' + IntToStr(ClientDataSet1.RecNo); end 3.Going 定位 GotoKey/GotoNearest 與FindKey/FindNearest基本上沒有什麼區別。它也是基於Index的查找。惟一的區別就是在於你是怎麼定義你的查找了。代碼上也有區別: ClientDataSet1.SetKey; ClientDataSet1.FieldByName(IndexFieldName).value := SearchText; ClientDataSet1.GotoKey; 就至關於 ClientDataSet1.FindKey([SearchText]); 要用好這兩種基於Index的查找,還須要瞭解ClientDataSet和Index機制。這裏就不詳細說明Index機制。一個基本的原則,要有Index,才能查找。 4.Locating 查找數據 2,3兩種查找方式都是基於Index的,可是在實際應用中,可能會查找IndexField之外的Field。那咱們就可使用Locate。可是查找速度是沒有2,3兩種快的。好比:若是你查找一條紀錄9000/10000,Locate須要500ms,Scanning須要>2s,FindKey只要10ms(可是當你打開ClientData的時候,創建Index須要1s)。 procedure TForm1.LocateBtnClick(Sender: TObject); begin Start; if ClientDataSet1.Locate('Field1,Field2..',VarArrayOf['value1,value2..'], []) then begin Done; StatusBar1.Panels[3].Text := 'Match located at record ' + IntToStr(ClientDataSet1.RecNo); end else begin Done; StatusBar1.Panels[3].Text := 'No match located'; end; end; ClientDataSet提供了好多種查找數據的方法。可是各自有其優缺點。 上面的例子中有Start;和Done,若是你有興趣,能夠加入計時點進行速度測試。 Scanning最簡單,可是最慢,由於比較慢,還得使用ClientDataSet.DisableControls和ClientDataSet.EnableControls方法(我在前面一片文章講過)。 Findkey/FindNearest(GotoKey/GotoNearest)代碼多,可是很是快。必須使用Index,不一樣的是Find須要的Index是必須創建好的,而Goto能夠在第一次使用時創建Index。 Locate使用最方便,不須要Index,可是速度沒有Find快。 var sFields : String; vResult : Variant; iCount : Integer; begin vResult := ds.Lookup('fieldnameA, fieldnameB' , VarArrayCreate([ValueA, ValueB], varVariant), 'fieldname1, fieldname2'); if (VarIsArray(vResult)) then begin sFields := ''; for iCount := VarArrayLowBound(vResult, 1) to VarArrayHighBound(vResult, 1) do begin sFields := sFields + ';' + vResult[iCount]; end; end else edtReturn.Text := vResult; end;