keyLife富翁筆記
作者: shiningplus
標題: delphi Midas SQLServer的自增字段的處理
關鍵字: delphi Midas 自增字段
分類: 個人專區
密級: 公開
(評分: , 回覆: 0, 閱讀: 1830) ??
delphi Midas SQLServer的自增字段的處理
1.新增時,表中有自增字段,但是不希望用Refresh,直接ApplyUpdates直接看見自增字段的值
在DataSetProvider.AfterUpdateRecord寫如下代碼
DataSetProvider.Options.poPropogateChanges:=True;
procedure TForm1.DataSetProvider1AfterUpdateRecord(Sender: TObject;
SourceDS: TDataSet; DeltaDS: TCustomClientDataSet;
UpdateKind: TUpdateKind);
begin
//DstId TADODataset
//FId 爲自增字段
if UpdateKind=ukInsert then
begin
DstId.CommandText:='select @@Identity as FId ';
DstId.Open;
DeltaDS.FieldByName('FId').ReadOnly:=False;
DeltaDS.FieldByName('FId').NewValue:=DstId.FieldByName('FId').AsInteger ;
DstId.Close;
end;
end;
2.
新增時,從表的關聯字段與主表的自增字段同步更新
procedure TProducts.DataSetProvider1BeforeUpdateRecord(Sender: TObject;
SourceDS: TDataSet; DeltaDS: TCustomClientDataSet;
UpdateKind: TUpdateKind; var Applied: Boolean);
begin
//DstProduct爲從表的Name
//CategoryID是從表的對於主表的字增自段的關聯字段
// qryIdentity是TADOQuery qryIdentity.SQL:='select @@identity'
if (UpdateKind = ukInsert) and
(SourceDS = DstProduct) and
(DeltaDS.FieldByName('CategoryID').Value = Unassigned) ) then
begin
if DeltaDS.BOF then
begin
qryIdentity.Close;
qryIdentity.Open;
end;
DeltaDS.FieldByName('CategoryID').NewValue := qryIdentity.Fields[0].Value;
end;
end;
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中,讓用戶的操作更方便。
多層結構中必不可少件TClientDataSet的全面剖析2008-12-11 15:01在三層結構中,TClientDataSet的地位是不可估量的,她的使用正確與否,是十分關鍵的,本文從以下幾個方面闡述她的使用,希望對你有所幫助.
1.動態索引
procedure TForm1.DBGrid1TitleClick(Column: TColumn);
begin
if (not column.Field is Tblobfield) then//Tblobfield不能索引,二進制
ClientDataSet1.IndexFieldNames:=column.Field.FieldName;
end;
2.多層結構中主從表的實現
設主表ClientDataSet1.packetrecord爲-1,所有記錄
設從表ClientDataSet1.packetrecord爲0,當前記錄
3.Taggregates使用
(1)在字段編輯中add new field類型爲aggregates
後設置expression(表達試)
設置active:=true即可
使用dbedit的field爲前者即可
(2)使用Aggergates屬性add設計表達試
調用
showmessage(floattostr(ClientDataSet1.Aggregates.Count));
showmessage(ClientDataSet1.Aggregates.Items[0].Value);
4.在單層數據庫中不要BDE
使用ClientDataSet代替table,使用ClientDataSet的loadfilename裝入cds
代替table的tablename的db或者dbf
原來的程序改造方法:
加一個ClientDataSet,使用右鍵assign locate data
後savetofile,再loadfromfile,後刪除table
將原連table的datasource設爲ClientDataSet
唯一注意的是:要將midas.dll拷到system或者當前目錄
5.三層結構的公文包的實現方法
同時設定1:filename(*.cds)2.remote server
6.可以對data賦值(從另一個數據集取值)
ClientDataSet2.Data:=ClientDataSet1.Data;
ClientDataSet2.Open;
或者
ClientDataSet2.CloneCursor(ClientDataSet1,true);
ClientDataSet2.Open;
7.附加數據取得
客戶程序嚮應用服務器請求數據。如果TClientDataSet 的
FetchOnDemand 屬性設爲True,
客戶程序會根據需要自動檢索附加的數據包如BLOB字段的值或嵌套表的內容。
否則,
客戶程序需要顯式地調用GetNextPacket 才能獲得這些附加的數據包。
ClientDataSet的packetrecords設置一次取得的記錄個數
8.ClientDataSet與服務器端query連接方法
(1)sql內容爲空
ClientDataSet1.Close;
ClientDataSet1.CommandText:=edit1.Text;//即sql內容
ClientDataSet1.Open;
對於沒有應用服務器設置filter 如:country like 'A%'
filtered=true可實現sql功能
(2)有參數
如服務端query的sql爲
select * from animals
where name like :dd
則:客戶端ClientDataSet
var
pm:Tparam;
begin
ClientDataSet1.Close;
ClientDataSet1.ProviderName:='DataSetProvider1';
pm:=Tparam.Create(nil);
pm.Name:='dd';
pm.DataType:=ftString;
ClientDataSet1.Params.Clear;
ClientDataSet1.Params.AddParam(pm);
ClientDataSet1.Params.ParamByName('dd').AsString:=edit1.Text ;
ClientDataSet1.Open;
pm.Free;
end;
9.數據的更新管理
(1)savepoint 保存目前爲止數據狀態,可以恢復到這個狀態
var
pp:integer;
begin
pp:=ClientDataSet1.SavePoint;
ClientDataSet1.Edit;
ClientDataSet1.FieldByName('姓名').asstring:='古話';
ClientDataSet1.Post;
table1.Refresh;
end;
恢復點
ClientDataSet1.SavePoint:=pp;
(2)cancel,RevertRecord
取消對當前記錄的修改,只適合沒有post的,如果post,調用
RevertRecord
(3)cancelupdate
取消對數據庫所有的修改
(4)UndoLastChange(boolean),changecount
取消上一次的修改,可以實現連續撤消
參數爲true:光標到恢復處
false:光標在當前位置不動
changecount返回修改記錄的次數,一個記錄修改多次,返回只一次
但UndoLastChange只撤消一次
10.可寫的recno
對於Ttable和Tquery的recno是隻讀的,而TClientDataSet的recno可讀可寫
ClientDataSet1.recno:=5;是設第五個記錄爲當前記錄
11.數據保存
對於table使用post可更新數據
而ClientDataSet1的post只更新內存數據,要更新服務器數據要使用
ApplyUpdates(MaxErrors: Integer),他有一個參數,是允許發出錯誤的
次數,-1表示無數次,使用simpleobjectbroker時常設爲0,實現自動容錯和負載平衡
ClientDataSet排序
1、簡單排序
ClientDataSet1.IndexFieldNames:='排序字段'
2、複雜排序(建立索引)
下面這個過程僅供參考(因爲用到三方控件DBGridEh):
procedure TDM1.DsSort(SortColumn: TColumnEh);
var
OldIndex:string;
begin
if (SortColumn.Grid.DataSource=nil) or (SortColumn.Grid.DataSource.DataSet=nil) or (not SortColumn.Grid.DataSource.DataSet.Active) then Exit;
OldIndex:=TClientDataSet(SortColumn.Field.DataSet).IndexName;
if OldIndex<>'' then
begin
TClientDataSet(SortColumn.Field.DataSet).IndexName:='';
TClientDataSet(SortColumn.Field.DataSet).DeleteIndex(OldIndex);
end;
case SortColumn.Title.SortMarker of
smNoneEh,
smUpEh :TClientDataSet(SortColumn.Field.DataSet).AddIndex('px',SortColumn.Field.FieldName,[ixDescending]);
smDownEh:TClientDataSet(SortColumn.Field.DataSet).AddIndex('px',SortColumn.Field.FieldName,[ixPrimary]);
end;
TClientDataSet(SortColumn.Field.DataSet).IndexName:='px';
end; 把上面的過程稍做修改,可用於標準DBGridvar
ASC:Boolean=True;//是否升序排列
procedure TDM1.DsSort(SortColumn: TColumn);
var
OldIndex:string;
begin
if (SortColumn.Grid.DataSource=nil) or (SortColumn.Grid.DataSource.DataSet=nil) or (not SortColumn.Grid.DataSource.DataSet.Active) then Exit;
OldIndex:=TClientDataSet(SortColumn.Field.DataSet).IndexName;
if OldIndex<>'' then
begin
TClientDataSet(SortColumn.Field.DataSet).IndexName:='';
TClientDataSet(SortColumn.Field.DataSet).DeleteIndex(OldIndex);
end;
case ASC of
True :TClientDataSet(SortColumn.Field.DataSet).AddIndex('px',SortColumn.Field.FieldName,[ixDescending]);//已經是升序就按降序排列
else//否則按升序排列
TClientDataSet(SortColumn.Field.DataSet).AddIndex('px',SortColumn.Field.FieldName,[ixPrimary]);
end;{end case}
TClientDataSet(SortColumn.Field.DataSet).IndexName:='px';
ASC:=not ASC;
end;
用於TABLEvar
ASC:Boolean=True;//是否升序排列
procedure DsSort(SortColumn: TColumn);
var
OldIndex:string;
begin
if (SortColumn.Grid.DataSource=nil) or
(SortColumn.Grid.DataSource.DataSet=nil) or
(not SortColumn.Grid.DataSource.DataSet.Active) then Exit;
OldIndex:=TTable(SortColumn.Field.DataSet).IndexName;
if OldIndex<>'' then
begin
TTable(SortColumn.Field.DataSet).IndexName:='';
TTable(SortColumn.Field.DataSet).DeleteIndex(OldIndex);
end;
try
TTable(SortColumn.Field.DataSet).DeleteIndex('px');
except
end;
case ASC of
True : TTable(SortColumn.Field.DataSet).AddIndex('px',
SortColumn.Field.FieldName,
[ixDescending]);//已經是升序就按降序排列
else//否則按升序排列
TTable(SortColumn.Field.DataSet).AddIndex('px',
SortColumn.Field.FieldName,
[ixPrimary]);
end;{end case}
TTable(SortColumn.Field.DataSet).IndexName:='px';
ASC:=not ASC;
end;
//當點擊DBGRID標題時調用
procedure TForm1.DBGrid1TitleClick(Column: TColumn);
begin
DsSort(Column);
end;
ClientDataSet的隱含功能------轉載《Delphi 從入門到精通》
可能與前面的筆記有重複的地方
ClientDataSet組件支持很多特性,其中一些與三級結構有關,而且還可以用在其他環境中。該組件說明了一個數據庫完全映象在內存中,這使得可以進行動態的操作,如建立一個索引,其他數據集合通常不支持該特性。例如,爲了對查詢分類,我們通常是重新執行它。爲了索引一個局部表格,需要定義索引。只有ADO數據集合有一些與ClientDataSet一樣的動態索引功能。
索引並不是ClientDataSet提供的全部功能。當我們擁有了索引之後,可以基於它定義組,可能是多級別的分組。對於確定一個記錄在組中的位置(頭、尾或中間位置),甚至有專門的支持。在組或整個數據表格中,我們可以定義總計;也就是說,可以動態計算整個表格或當前組中一列的總和或平均值。數據不需要發送給物理服務器,因爲這些總計操作發生在內存中。我們甚至可以定義新的總計字段,可以直接與數據敏感控件相連。
注意,所有這些特性不但可以用與MIDAS應用程序,還可以用與客戶機/服務器,甚至是局部瘦應用程序。事實上,ClientDataSet組件可以從遠程MIDAS連接、局部數據集合(建立起數據的快照)、或局部文件(就象在公文包模式中一樣,但使用的只是在客戶機數據集合中定義的整個表格)中獲得起數據。
這是另一個需要研究的領域,所以將向讀者演示兩個範例來突出關鍵特性。這些範例沒有基於MIDAS,而是基於局部表格。
1、定義抽象的數據的數據類型
VCL數據庫支持的一個有趣的特性是,當我們基於局部文件使用ClientDataSet時,可以定義抽象的數據類型。只需在窗體上放置一個ClientDataSet組件,爲FieldDefs屬性激活編輯器,添加兩個字段,併爲他們的DataType屬性選擇ftADT值。現在,移到ChildDefs屬性,並定義子字段,下面是AdtDemo範例的字段定義:
FieldDefs = <
item
Name = 'ID'
DataType = ftInteger
end
item
name = 'Name'
ChildDefs = <
item
name = 'LastName'
DataType = ftString
size = 20
end
item
name = 'FirstName'
datatype = ftString
size = 20
end>
datatype = ftADT
size = 2
end>
在此,只需爲ClientDataSet的FileName屬性輸入一個名稱,用鼠標右鍵單擊組件,並選擇Create Table命令即可;我們準備編譯並運行應用程序(在向它連接數據敏感組件之後)。數據會自動從提供的文件中讀取,關閉程序時會將變化保存在文件中。
如果使用DBGrid查看結果數據集合,它允許我們展開或壓縮ADT字段的子字段。我們可以通過定義字段的OnGetText事件提供它的壓縮值(在Delphi4 中有一個缺省值,但Delphi5中沒有):
procedure TForm1.ClientDataSet1NameGetText(Sender:TField;
var Text:String;DisplayText:Boolean);
begin
Text:=ClientDataSet1NameFirstName.AsString+' '+
ClientDataSet1NameLastName.AsString;
end;
2、動態索引
一旦ClientDataSet上有了數據,數據就已全部處於內存中了。當我們將組件基於局部文件中時(如在AdtDemo範例中),在程序啓動時整個文件就被裝載到了內存總。這與從Paradox數據表格中裝載數據(BDE只裝載正訪問的字段)不同。
將整個表格裝在內存中的優點是,我們可以快速地對它進行分類。使用ClientDataSet組件,我們可以通過賦給IndexFieldNames屬性相應的字段名來實現分類。在AdtDemo(以及很多程序)中,該索引變動會在單擊DBGrid控件的標題(觸發OnTitleClick事件)時執行:
procedure TForm1.DBGrid1TitleClick(Column:TColumn);
begin
if Column.Field.FullName = 'Name' then
ClientDataSet1.IndexFieldNames := 'Name.LastName'
else
ClientDataSet1.IndexFieldNames := Column.Field.FullName;
end;
由於ADT定義,程序使用了字段的FullName屬性(而不是FieldName屬性)。事實上,對於子字段來說,索引應該基於Name.LastName,而不是LastName。而且ADT字段不能自己被索引,所以如果選擇它,程序會使用LastName子字段作爲索引。這些索引不是持久性的;它們沒有保存在文件中,而只是在內存中應用於數據。
技巧:ClientDataSet可以擁有基於計算字段的索引,特別是內部計算字段,這種字段類型只能用於該數據集合。
3、分組
一旦爲ClientDataSet定義了一個索引,就可以通過該索引對數據進行分組了。實際上,一組被定義爲連續記錄的一個列表(根據索引),記錄中被索引的字段的值不會改變。例如,如果有一個基於國家的索引,帶有該國家的所有地址都將歸爲一組。
cdsCalcs範例有一個ClientDataSet組件,它同樣從DBDEMOS數據庫的Country表格中讀取其數據。該操作可以在設計時,使用ClientDataSet組件快捷菜單的Assign Local Data命令來執行。爲了在運行時讀取數據,獲得一個更新的快照,可以向窗體添加一個DataSetProvider組件,如下連接三個組件:
Object Table :TTable
active = true
databasename = 'dbdemos'
tablename = 'country.db'
end
object datasetprovider1: TDataSetProvider
dataset = table1
end
object clientdataset1: tclientdataset
providername = 'datasetprovider1'
end
現在我們來看看組的定義。該定義可以通過爲索引指定一個分組級別,與索引定義一起獲得:
object clientdataset1: tclientdataset
indexdefs = <
item
name = 'clientdataset1index1'
fields = 'continent'
groupinglevel = 1
end>
indexname = 'clientdtaset1index1'
當擁有了一組之後,我們可以在DBGrid中向用戶顯示分組結構。只需爲分組字段(在範例中是Continent字段)處理OnGetText事件,只有當記錄是組的第一個記錄是才顯示文本:
procedure TForm1.ClientDataSet1ContinentGetText(Sender:TField;
var Text:String;DisplayText:Boolean);
begin
if gbFirst in ClientDataSet1.GetGroupState(1) then
Text:= sender.asstring
else
text:='';
end;
4、定義合計
ClientDataSet組件另一個功能強大的特性是對合計的支持。合計是一個基於多個記錄的計算值,如整個數據表格或一組記錄(使用我們剛纔討論過的分組邏輯來定義)中某個字段的和值或平均值。合計是可持續的;也就是說,如果有一個記錄發生改變,會立刻重新計算合計值。例如,當擁護在發貨清單條目中輸入時,發貨單的總和會自動被重新計算出來。
注意:::合計是遞增維持的,而不是每當有一個值改動時就重新計算所有的值。合計的更新利用了ClientDataSet追蹤的Delta。例如,當字段發生改變時,爲了更新Sum,ClientDataSet會從合計中讀取舊值,並加上新值。只需要兩次計算,即使在該合計組中有上千行。因此,合計更新是瞬時的。
有兩種方法定義合計。我們可以使用ClientDataSet(是一個集合)的Aggregates屬性,或可以使用Fields編輯器定義合計字段。在這兩種情況下,我們定義的合計表達式,賦給它一個名稱,並將它與一個索引和一個分組級別(除非想將它應用於整個數據表格)連接。下面是CdsCalcs範例的Aggregates集合:
Object ClientDataSet1: TClientDataSet
Aggregates = <
item
Active = True
AggregateName = 'Count'
Expression = 'Count(Name)'
GroupingLevel = 1
IndexName = 'ClientDataSet1Index1'
Visible = False
end
item
Active = True
AggregateName = 'TotalPopulation'
Expression = 'SUM(POPULATION)'
Visible = False
end>
AggregatesActive = True
注意,在上面的最後一行代碼中,除了激活每個想使用的特定合計之外,我們還必須爲合計激活支持。解除合計是重要的,因爲合計太多會減慢程序執行的速度。我們曾提到的另一種方法是使用Fields編輯器,在其快捷菜單中選擇New Field命令,並選擇Aggregate選項(只有在一個ClientDataSet中可以與InternalCalc選項一起使用)。下面是一個合計字段的定義:
Object ClientDataSet1: TClientDataSet
object ClientDataSet1TotalArea: TAggregateField
FieldName = 'TotalArea'
ReadOnly = True
Visible = True
Active = True
DisplayFormat = '###,###,###'
Expression = 'SUM(AREA)'
GroupingLevel = 1
IndexName = 'ClientDataSet1Index1'
end
合計字段在Fields編輯器中被顯示爲獨立的一組。與普通合計相比,使用合計字段的優點是,我們可以定義顯示格式,並將字段直接與數據敏感控件相連,如CdsCalcs範例中的DBEdit。因爲合計與一個組相連,所以要選擇了另一組的記錄,輸出就會被自動更新。而且,如果改變數據,合計值也會立刻顯示新值。
爲了使用普通合計,必須編寫一些代碼,如下例子中所示(注意合計的Value是一個變體):
procedure TForm1.Button1Click(Sender: TObject);
begin
Label1.Caption:= 'Area : '+ ClientDataSet1TotalArea.DisplayText +
#13'Population : ' + FormatFloat('###,###,###',ClientDataSet1.Aggregates[1].Value)
+ #13'Number : ' + IntToStr(ClientDataSet1.Aggregates[0].Value);
end;