第六章 什麼是資料集
Delphi 4中有四種類型的标準資料集構件,分别是TTable、TQuery、TStoredProc和TClientDataSet。這些資料集構件都是從一個共同的基類TDataSet繼承下來的,其中,隻有TClientDataSet是直接從TDataSet繼承下來的,而TTable、TQuery、TStoredProc的直接上級是TDBDataSet,TDBDataSet的上級是TBDEDataSet,TBDEDataSet 的上級才是TDataSet。這幾個類之間的繼承關系可以用圖6.1來表示。
圖6.1 資料集的繼承關系
TDataSet是所有資料集的抽象基類,它的大部分屬性和方法是虛拟的或抽象的。所謂虛拟的方法,是指這些方法可以被派生類重載。所謂抽象的方法,是指這些方法隻有聲明,沒有定義,派生類必須給出定義後才能調用這些方法,不同的派生類可以有不同的定義。
由于TDataSet中包含抽象的方法,您不能直接建立它的執行個體,否則會引起運作期錯誤。
如果從功能上劃分,TDataSet的屬性和方法可以分為這麼幾大塊:打開和關閉資料集、浏覽記錄、編輯資料、書簽管理、控制連接配接、通路字段、記錄緩沖區管理、過濾、事件。
<b>6.1 打開和關閉資料集</b>
在對資料集進行任何操作之前,首先要打開資料集。要打開資料集,可以把Active屬性設為True,例如:
CustTable.Active := True;
也可以調用Open函數,例如:CustQuery.Open;
要關閉資料集,可以把Active屬性設為False或者調用Close函數。
<b>6.2 資料集的狀态</b>
資料集的狀态(State屬性)決定了目前能夠對資料集進行的操作,例如,當資料集已經關閉,它的狀态是dsInactive,此時就不能通路資料集的任何資料。
<b>6.2.1 State屬性</b>
State屬性是隻讀的,下面列出了State屬性可能的值:
.dsInactive 資料集已關閉,不能通路它的資料;
.dsBrowse 資料集已打開,可以浏覽資料但不能修改資料;
.dsEdit 此時為編輯狀态,可以修改資料;
.dsInsert 此時可以插入一條新的記錄;
.dsSetKey 隻适用于TTable和TClientDataSet,此時可以設定範圍和鍵值,并且可以調用GotoKey函數;
.dsCalcFields 正在處理OnCalcFields事件,此時不能修改非計算字段的值;
.dsCurValue 内部使用;
.dsNewValue 内部使用;
.dsOldValue 内部使用;
.dsFilter 正在進行過濾操作。
當一個資料集剛剛打開的時候,它的State屬性被設為dsBrowse,以後,State屬性的值會随着應用程式的操作自動變化。
要使資料集進入dsBrowse、dsEdit、dsInsert或dsSetKey狀态,就得調用相應的方法。
例如,要使資料集CustTable進入dsInsert狀态,程式示例如下:
Procedure TForm1.InsertButtonClick(Sender: TObject);
Begin
CustTable.Insert;{進入dsInsert狀态}
AddressPromptDialog.ShowModal;
If AddressPromptDialog.ModalResult := mrOK then
CustTable.Post; {恢複為dsBrowse狀态}
Else
CustTable.Cancel; {恢複為dsBrowse狀态}
End;
從上面這個例子可以看出,有些操作會使資料集自動變成dsBrowse狀态,例如,調用Post函數如果成功的話,資料集就恢複為dsBrowse狀态,如果調用Post沒有成功,資料集仍然保持原來的狀态。調用Cancel也能使資料集恢複為dsBrowse狀态。
如果把Active屬性設為False,或者調用Close,将使資料集進入dsInactive狀态。例如,下面兩行代碼是等價的:
CustTable.Active := False;
CustTable.Close;
有些狀态如dsCalcFields、dsCurValue、dsNewValue、dsOldValue和dsFilter不能被應用程式所控制,而是由資料集本身根據需要自動設定的。例如,當正在處理OnCalcFields事件時,就自動進入dsCalcFields狀态。當退出處理OnCalcFields事件的句柄時,資料集自動恢複成原先的狀态。
當資料集的狀态發生改變時,會觸發TDataSource構件的OnStateChange事件,如果這個TDataSource構件的DataSet屬性指定了這個資料集的話。
下面詳細介紹資料集的各種狀态以及怎樣進入這些狀态。
<b>6.2.2 dsInactive狀态</b>
當資料集已關閉時,就處于dsInactive狀态。此時,不能通路它的任何資料。
要使資料集進入dsInactive狀态,可以把Active屬性設為False,或者調用Close。在資料集将要關閉之前,會觸發BeforeClose事件。當資料集剛剛關閉,會觸發AfterClose事件。如果在資料集處于dsEdit或dsInsert狀态時調用Close,應當在處理BeforeClose事件的句柄中提示使用者是認可還是取消。程式示例如下:
Procedure CustTable.VerifyBeforeClose(DataSet: TDataSet)
If (CustTable.State = dsEdit) or (CustTable.State = dsInsert) then
If MessageDlg('認可修改嗎?', mtConfirmation, mbYesNo, 0) = mrYes then
CustTable.Post;
ElseCustTable.Cancel;
<b>6.2.3 dsBrowse狀态</b>
當一個資料集剛剛打開的時候,資料集總是處于dsBrowse狀态,此時,可以顯示資料集中的記錄,但不能編輯和插入記錄。
dsBrowse狀态可以認為是資料集的基本狀态,在此狀态下,可以進入其他狀态。例如,調用Insert或Append函數将使資料集的狀态從dsBrowse變成dsInsert(當然,這還取決于其他因素,如CanModify屬性的值),調用SetKey将使資料集從dsBrowse變成dsSetKey狀态。
TDataSet有兩個方法可以使資料集回到dsBrowse狀态,一個是Cancel,它将取消目前正在進行的編輯、插入、搜尋等操作,使資料集回到dsBrowse狀态。另一個是Post,它将試圖把修改了的資料儲存到資料集中,如果成功的話,資料集将回到dsBrowse狀态,如果沒有成功,資料集仍然保持原先的狀态。
<b>6.2.4 dsEdit狀态</b>
如果應用程式要修改資料集的資料,必須首先進入dsEdit狀态。要進入dsEdit狀态,可以調用Edit。不過,調用Edit并不能保證一定能進入dsEdit狀态,這還取決于CanModify屬性的值。如果這個屬性傳回True的話,表示資料集是可以讀和寫的。
對于TTable構件來說,如果ReadOnly屬性設為True,CanModify屬性肯定傳回False。對于TQuery構件來說,如果RequestLive屬性設為False,CanModify屬性肯定傳回False。
即使資料集進入了dsEdit狀态,也并不意味着使用者就一定能修改資料,資料控件的ReadOnly屬性還必須設為False。此外,對于SQL資料庫來說,使用者能不能修改資料還取決于使用者有沒有修改資料的權限。
要從dsEdit狀态傳回到dsBrowse狀态,可以調用Cancel、Post或Delete函數,其中,如果Post和Delete沒有調用成功的話,就仍然保持dsEdit狀态。
在資料控件中,當使用者修改了資料後把輸入焦點移走,就相當于調用Post函數,将使資料集回到dsBrowse狀态。
<b>6.2.5 dsInsert狀态</b>
如果應用程式要插入新的記錄,必須首先進入dsInsert狀态。要進入dsInsert狀态,可以調用Insert或Append函數。不過,調用Insert或Append并不能保證一定能進入dsInsert狀态,這還取決于CanModify屬性的值是否傳回True。
即使資料集進入了dsInsert狀态,也并不意味着使用者就一定能插入記錄,資料控件的ReadOnly屬性還必須設為False。此外,對于SQL資料庫來說,使用者能不能插入記錄還取決于使用者有沒有修改資料的權限。
要從dsInsert狀态傳回到dsBrowse狀态,可以調用Cancel、Post或Delete函數,其中,如果Post沒有調用成功的話,就仍然保持dsInsert狀态。
在資料控件中,當使用者修改了資料後把輸入焦點移走,就相當于調用Post,将使資料集回到dsBrowse狀态。
<b>6.2.6 dsSetKey狀态</b>
可以調用Locate、Lookup在資料集中搜尋特定的記錄。對于TTable構件來說,還可以調用GotoKey、GotoNearest、FindKey或FindNearest在表格中搜尋特定的記錄。在調用上述方法之前,必須首先使資料集進入dsSetKey狀态。要使資料集進入dsSetKey狀态,可以調用SetKey。調用了上述方法後,資料集又回到dsBrowse狀态。
另外,可以對資料集進行過濾。對于TTable構件來說,還可以預先設定範圍。在進行過濾和範圍操作前,也要首先進入dsSetKey狀态。
<b>6.2.7 dsCalcFields狀态</b>
當OnCalcFields事件被觸發時,就會使資料集進入dsCalcFields狀态。在處理OnCalcFields事件的句柄中,應當給出“計算字段”的值。
在dsCalcFields狀态下,除了“計算字段”外,應用程式不能修改其他字段的值,因為如果其他字段的值發生變化,又會導緻OnCalcFields事件被觸發,進而導緻無限循環。
當OnCalcFields事件處理完畢,資料集又回到dsBrowse狀态。
<b>6.2.8 dsFilter狀态</b>
當OnFilterRecord事件被觸發時,就會使資料集進入dsFilter狀态。在此狀态下,不允許修改資料集的記錄,否則,過濾就無法正确執行。
當OnFilterRecord事件處理完畢,資料集就回到dsBrowse狀态。
<b>6.2.9 dsNewValue、dsOldValue或dsCurValue</b>
在允許緩存更新的情況下,當使用者修改資料集的記錄時,資料集有可能會進入dsNewValue、dsOldValue或dsCurValue狀态。在這三種狀态下,可以通過字段(TField)的NewValue、OldValue或CurValue屬性來通路當時的值。
上述三個狀态是由Delphi 4内部使用的,應用程式無法主動進入上述三種狀态。
<b>6.3 浏 覽 記 錄</b>
每個活動的資料集都有一個指針,指向目前記錄。很多對資料集的操作都是針對目前記錄,許多資料控件也隻顯示目前記錄的資料,是以,在資料庫應用程式中知道目前記錄的位置是非常重要的。
資料庫應用程式往往要改變目前記錄的位置,這時候就要用到下面這些方法:
.First 使第一條記錄成為目前記錄;
.Last 使最後一條記錄成為目前記錄;
.Next 使下一條記錄成為目前記錄;
.Prior 使前一條記錄成為目前記錄;
.MoveBy 使距離目前記錄若幹行的記錄成為目前記錄。
此外,Delphi 4中有一個TDBNavigator構件,專門用來浏覽記錄,它把上述方法用按鈕來實作。
除了上述方法外,TDataSet中還有兩個隻讀的布爾類型的屬性用來判斷目前記錄的位置,一個是Bof,如果這個屬性傳回True,表示現在已到了資料集的開始位置。另一個是Eof,如果這個屬性傳回True,表示現在已到了資料集的末尾。
<b>6.3.1 First和Last</b>
調用First函數能夠使資料集的第一條記錄成為目前記錄,并且把Bof屬性設為True。如果第一條記錄已經是目前記錄了,First就什麼也不幹。程式示例如下:
CustTable.First;
調用Last函數能夠使資料集的最後一條記錄成為目前記錄,并且把Eof屬性設為True。如果最後一條記錄已經是目前記錄,Last就什麼也不幹。程式示例如下:
CustTable.Last;
用TDBNavigator構件實作的導航器上有兩個按鈕,分别對應着First和Last。
<b>6.3.2 Next和Prior</b>
調用Next函數能夠使下一條記錄成為目前記錄。如果目前記錄已經是資料集的最後一條記錄,Next就什麼也不幹。程式示例如下:
CustTable.Next;
調用Prior能夠使前一條記錄成為目前記錄。如果目前記錄已經是資料集的第一條記錄,Prior就什麼也不幹。程式示例如下:
CustTable.Prior;
<b>6.3.3 MoveBy</b>
調用MoveBy函數使資料集中的另一條記錄成為目前記錄,該記錄距原先的目前記錄若幹行。MoveBy需要傳遞一個參數,指定相隔的行數,正數表示向記錄編号增大的方向移動,負數表示向記錄編号減小的方向移動。程式示例如下:
CustTable.MoveBy(-2);
MoveBy傳回實際移動的行數,傳回值與傳遞給MoveBy的參數有可能不同。
注意:在多使用者環境下,其他使用者有可能正在修改、插入或删除記錄,這樣,一條記錄原來距目前記錄是5行,現在有可能變為4行和6行,甚至該記錄都不存在了,因為其他使用者修改該記錄的資料或删除了該記錄。
<b>6.3.4 Eof和Bof屬性</b>
TDataSet有兩個隻讀的屬性Eof和Bof,分别用于判斷是否到了資料集的末尾和開頭。在周遊資料集的所有記錄時經常要用到這兩個屬性。
如果Eof屬性傳回True,表示現在已到了資料集的末尾。
進行下列操作時會把Eof屬性設為True:
.打開一個空的資料集;
.調用了Last;
.調用了Next,而現在已經在資料集的最後一條記錄;
.調用了SetRange,而範圍是無效的。
除了上述情況外,Eof屬性都将傳回False。
Eof屬性通常用在一個循環中,每調用一次Next就要判斷一下Eof屬性,以避免對不存在的記錄進行操作。程式示例如下:
CustTable.DisableControls;
Try
CustTable.First;
While not CustTable.EOF Do
...
CustTable.Next;
Finally
CustTable.EnableControls;
上述代碼同時示範了在周遊資料集的所有記錄時怎樣暫時禁止資料控件跟着重新整理。在周遊資料集的所有記錄前應當調用DisableControls禁止重新整理,這樣能夠加快周遊的速度,因為重新整理也是要花時間的。周遊結束後,應當調用EnableControls恢複重新整理。
EnableControls最好在Try...Finally結構的Finally部分調用,這樣能保證即使在周遊時出現異常,也能保證重新整理能得到恢複。
如果Bof屬性傳回True,表示現在已到了資料集的開頭。
進行下列操作時會把Bof屬性設為True:
.打開一個非空的資料集;
.調用了First;
.調用了Prior,而現在已經在資料集的第一條記錄。
除了上述情況外,Bof屬性都将傳回False。與Eof屬性一樣,Bof屬性通常用在一個循環中,每調用一次Prior就要判斷一下Bof屬性,以避免對不存在的記錄進行操作。程式示例如下:
While not CustTable.BOF Do
CustTable.Prior;
<b>6.4 書 簽</b>
書簽的作用是在資料集的某個位置做一個标記,以後可以快速友善地回到那個位置。TDataSet中提供了若幹個屬性和方法用于管理書簽。
如果讀Bookmark屬性,傳回目前記錄的書簽。如果寫Bookmark屬性,它能使一個指定的書簽成為目前書簽。
TDataSet中有關書簽的幾個函數都是虛拟的,TDataSet的派生類TBDEDataSet重新定義了這些方法,包括:
.BookmarkValid判斷某個書簽是否合法;
.CompareBookmarks比較兩個書簽是否相同;
.GetBookmark建立一個書簽來标記目前記錄;
.GotoBookmark回到用GetBookmark标記的位置;
.FreeBookmark删除一個書簽。
要建立一個書簽,首先要聲明一個TBookmark類型的變量,然後調用GetBookmark函數建立一個标記目前記錄的書簽。TBookmark類型的變量實際上是一個無類型的指針。
在調用GotoBookmark之前,最好先調用BookmarkValid判斷書簽是否合法,因為書簽标記的記錄有可能已删掉。如果BookmarkValid傳回True,說明書簽是合法的,可以調用GotoBookmark跳轉到書簽标記的位置。
可以調用CompareBookmarks比較兩個書簽是否相同,如果兩個書簽不同,這個函數就傳回1。如果兩個書簽相同或者都是NIL,這個函數就傳回0。
GotoBookmark需要傳遞一個參數,即書簽。
FreeBookmark用于删除一個書簽。當一個書簽已用不到時,應當及時删除它,因為書簽也是一種資源。
下面這段代碼示範了書簽的用法:
Procedure DoSomething (const Tbl: TTable)varBookmark: TBookmark;
Bookmark := Tbl.GetBookmark;
Tbl.DisableControls;
Tbl.First;
While not Tbl.EOF Do
Tbl.Next;
Tbl.GotoBookmark(Bookmark);
Tbl.EnableControls;
Tbl.FreeBookmark(Bookmark);
<b>6.5 搜尋特定的記錄</b>
可以調用Locate和Lookup函數在資料集中搜尋特定的記錄。
Locate用于在資料集中定位一條特定的記錄,并使該記錄成為目前記錄。Locate需要傳遞三個參數,第一個是KeyFields參數,用于指定要按哪些字段搜尋,第二個是KeyValues參數,用于指定每個字段相應的值,第三個是Options參數,用于設定搜尋選項。
下面這個例子搜尋Company字段的值為“Professional Ltd.”的記錄:
var
LocateSuccess: Boolean;
SearchOptions: TLocateOptions;
SearchOptions := [loPartialKey];
LocateSuccess := CustTable.Locate('Company', 'Professional Ltd.',SearchOptions);
如果Locate找到了一條符合條件的記錄,就把該記錄變為目前記錄,并傳回True。如果Locate沒有找到比對的記錄,就傳回False。
對于Locate來說,KeyFields參數指定的字段越多,搜尋的條件就越精确。如果KeyFields參數需要指定多個字段,彼此之間要用分号分開。由于字段的資料類型可能各不相同,是以,KeyValues是一個Variant類型的參數。如果KeyFields參數指定了多個字段,KeyValues參數必須是一個可變類型的數組。程式示例如下:
With CustTable Do
Locate('Company;Contact;Phone', VarArrayOf(['Sight Diver','P']), loPartialKey);
Lookup與Locate非常相似,也是在資料集中搜尋特定的記錄。不同的是,如果找到比對的記錄,Lookup能傳回該記錄中若幹個字段的值。
Lookup需要傳遞三個參數,第一個是KeyFields參數,用于指定要按哪些字段搜尋,第二個是KeyValues參數,用于指定每個字段相應的值,第三個是ResultFields參數,用于指定要傳回哪些字段的值。
下面這個例子在CustTable中搜尋Company字段的值為“Professional Ltd.”的記錄,并傳回Company、Contact、Phone等字段的值:
LookupResults: Variant;
With CustTable Do
LookupResults := Lookup('Company', 'Professional Divers, Ltd.', 'Company;Contact; Phone');
如果ResultFields參數指定了多個字段,Lookup傳回一個可變類型的數組。如果沒有找到比對的記錄,Lookup将傳回一個空的數組。程式示例如下:
LookupResults := Lookup('Company; City', VarArrayOf(['Sight Diver',
'Christiansted']), 'Company; Addr1; Addr2; State; Zip');
<b>6.6 過 濾</b>
一個應用程式往往隻對資料集的部分記錄感興趣,例如,可能隻對一個客戶表中來自廣東的客戶感興趣。這種情況下,可以用過濾技術把符合特定條件的記錄過濾出來。
不過,對于一個字段很多的資料集來說,最好還是使用查詢。
<b>6.6.1 允許過濾</b>
要對資料集進行過濾,首先要指定過濾條件,并設定FilterOptions屬性設定有關選項(可選),然後把Filtered屬性設為True。以後如果不想進行過濾,隻要把Filtered屬性設為False。
要指定過濾條件有兩種方式:一是設定Filter屬性,二是在處理OnFilterRecord事件的句柄中給出過濾條件。
Filter屬性适合于在運作期使用,它能夠動态地指定過濾條件,能夠根據需要改變過濾條件。不過,Filter屬性是一個字元串,過濾條件相對比較簡單。雖然可以用運算符構成複合的條件表達式,但隻限于幾個常見的運算符。更主要的是,用Filter屬性指定的表達式中隻能出現資料集中已有的字段名和常量,不能出現其他資料。
而OnFilterRecord事件則靈活得多,它能夠在設計期就指定好過濾條件。而在處理OnFilterRecord事件的句柄中,可以任意指定過濾條件,過濾條件可以很複雜。
<b>6.6.2 Filter屬性</b>
Filter屬性是一個字元串,可以這樣設定Filter屬性:
Dataset1.Filter := '''State'' = ''CA''';
也可以這樣設定Filter屬性:
Dataset1.Filter := Edit1.Text;
上面這行代碼允許讓使用者自己輸入過濾條件。甚至還可以把上述兩行代碼結合起來:
Dataset1.Filter := '''State'' = ' + Edit1.Text;
設定了過濾條件後,隻要把Filtered屬性設為True,過濾即有效。
可以用比較和邏輯運算符構造複合的過濾條件,這些運算符包括:
. <小于;
. >大于;
. >=大于等于;
. <=小于等于;
.=等于;
. <>不等于;
.AND兩邊的表達式都必須為True;
.NOT表達式不能為True;
.OR兩個表達式隻要有一個為True。
下面這個例子用AND運算符限制CustNo字段必須大于1400且小于1500:
(CustNo > 1400) AND (CustNo < 1500);
注意:在Filtered屬性設為True的情況下,使用者修改、插入的記錄有可能不符合過濾的條件,這時候,會拒絕接受與過濾條件沖突的記錄。
<b>6.6.3 OnFilterRecord事件</b>
在Filtered屬性設為True的情況下,資料集中的每條記錄都會觸發OnFilterRecord事件,這樣,就有機會決定是否要把記錄過濾。
處理OnFilterRecord事件的句柄中有一個布爾類型的Accept參數,把這個參數設為True表示接受此記錄,把這個參數設為False表示把此記錄過濾掉。程式示例如下:
Procedure TForm1.Table1FilterRecord(DataSet: TDataSet;
var Accept: Boolean);
Accept := DataSet['State'] = 'CA';
上面這個例子的意思是,隻有State字段的值為CA的記錄才被接受。
注意:由于資料集的每條記錄都會觸發OnFilterRecord事件,是以,處理OnFilterRecord事件的代碼要盡可能地簡短,尤其是對一個有很多條記錄的大型資料集。
有時候,程式需要按多種不同的過濾條件進行過濾,可以建立多個處理OnFilterRecord事件的句柄,然後在運作期動态地切換事件句柄,程式示例如下:
DataSet1.OnFilterRecord := NewYorkFilter;Refresh;
<b>6.6.4 設定過濾選項</b>
FilterOptions屬性用于設定過濾的選項。這個屬性是一個集合,可以是空集(預設),也可以包含下列元素:
.foCaseInsensitive比較字元串時忽略大小寫;
.foPartialCompare對于字元串類型的字段必須全字比對,不允許部分比對。
例如,為了在比較State字段時忽略大小寫,可以這樣設定:
FilterOptions := [foCaseInsensitive];
Filter := '''State'' = ''CA''';
<b>6.6.5 在過濾後的資料集中浏覽記錄</b>
過濾後的資料集實際上是原來的資料集的一個子集。TDataSet提供了四個方法用于在過濾後的資料集中浏覽記錄,它們是:
.FindFirst使過濾後的資料集中的第一條記錄成為目前記錄;
.FindLast使過濾後的資料集中的最後一條記錄成為目前記錄;
.FindNext使過濾後的資料集中的下一條記錄成為目前記錄;
.FindPrior使過濾後的資料集中的前一條記錄成為目前記錄。
上述四個方法如果調用成功,就傳回True,否則,就傳回False。可以檢查一個隻讀的Found屬性,看看上次調用是否成功。
如果通過Filter屬性或OnFilterRecord事件設定了過濾條件,而Filtered屬性設為False,調用上述四個方法時會自動暫時允許過濾,然後移動目前記錄的位置,最後又禁止過濾。換句話說,上述四個方法可以不理會Filtered屬性是怎樣設定的。
如果沒有設定過濾條件,上述四個方法即相當于First、Last、Next和Prior。
<b>6.7 修 改 數 據</b>
TDataSet中提供了一些方法用于在資料集中更新、插入和删除記錄,它們是:
.Edit使資料集進入dsEdit狀态;
.Append在資料集的末尾添加一條記錄;
.Insert在資料集的目前位置插入一條記錄;
.Post試圖把使用者對資料的修改寫到資料集中;
.Cancel取消使用者對資料的修改,使資料集回到dsBrowse狀态;
.Delete删除目前記錄。
<b>6.7.1 進入dsEdit狀态</b>
要編輯資料集的記錄,首先要進入dsEdit狀态。要進入dsEdit狀态,調用Edit函數。不過,調用Edit不一定會使資料集進入dsEdit狀态,還取決于CanModify屬性的值。
一旦資料集進入了dsEdit狀态,使用者就可以在資料控件上修改目前記錄的值。當使用者把輸入焦點從目前記錄上移走,即相當于調用了Post函數。程式示例如下:
Edit;
FieldValues['CustNo'] := 1234;
Post;
要取消目前未決的修改,使用者可以按ESC鍵或單擊用TDBNavigator構件實作的導航器上的Cancel按鈕。
在使用緩存更新技術(CachedUpdates屬性設為True)的情況下,調用Post隻是把資料寫到緩存中,而不是直接寫到資料集中。要把緩存中的資料寫到資料集中,需調用ApplyUpdates函數。
<b>6.7.2 插入新的記錄</b>
要在資料集中插入新的記錄,首先要進入dsInsert狀态。要進入dsInsert狀态,可以調用Insert或Append函數。不過,調用Insert或Append不一定會使資料集進入dsInsert狀态,還取決于CanModify屬性的值。
一旦進入了dsInsert狀态,使用者就可以在資料控件(一般是TDBGrid)中插入一條新的記錄,并給這條記錄輸入資料。
如果要通過程式設計來插入新的記錄,就要注意Insert和Append的差別。Insert将把一條新的記錄插入到目前記錄的前面,而Append将把一條新的記錄添加到資料集的末尾。
插入了新的記錄後,應當調用Post或在CachedUpdates屬性設為True的情況下調用ApplyUpdates把新的記錄寫到資料集中。
如果資料集是已建立了索引的Paradox或dBASE表,新記錄将自動移到恰當的位置。
如果資料集沒有建立索引,新記錄就插入到資料集的目前位置(Insert)或末尾(Append)。
<b>6.7.3 删除記錄</b>
調用Delete函數将删除目前記錄,并且使資料集回到dsBrowse狀态。如果窗體上有TDBNavigator構件的話,使用者可以單擊導航器上的“Delete”按鈕删除目前記錄。目前記錄被删除後,下一條記錄就成為目前記錄。
如果删除的本來就是最後一條記錄,則前一條記錄成為目前記錄。
<b>6.7.4 修改整條記錄</b>
除了TDBGrid和TDBNavigator外,大部分資料控件隻能工作于資料集的一個或幾個字段,而不是整條記錄。
不過,TDataSet提供了若幹個方法可以直接修改整條記錄而不是單獨的字段,這些方法包括:
.AppendRecord類似于Append,但可以給字段指派,不需要調用Post;
.InsertRecord類似于Insert,但可以給字段指派,不需要調用Post;
.SetFields對目前記錄的字段指派,需要顯式地調用Post。
上述三個方法都要傳遞一個TVarRec類型的數組作為參數,該數組的每一個元素對應着一個字段的值。如果數組的元素個數小于資料集的字段個數,剩下字段的值就是NULL。
對于沒有建立索引的資料集來說,AppendRecord把一條新的記錄加到資料集的末尾。對于已建立索引的資料集來說,新記錄将自動移到一個恰當的位置。
SetFields用于對目前記錄的字段指派。在調用SetFields之前,首先要調用Edit,使資料集進入dsEdit狀态。調用了SetFields後,需要顯式地調用Post函數。
調用SetFields時,如果您隻想對部分字段指派,讓其他字段的值保持不變,可以用NULL或NIL去指派。
假設一個資料集中有五個字段,分别是Name、Capital、Continent、Area和Population,可以這樣對它們指派:
CountryTable.InsertRecord(['Japan', 'Tokyo', 'Asia']);
上述程式在資料集中插入了一條新的記錄,并且對前三個字段賦了值。現在可以再次對目前記錄指派,不過,這次隻想對Area字段和Population字段指派,程式就要這樣寫:
With CountryTable Do
If Locate('Name', 'Japan', loCaseInsensitive) then
SetFields(NIL, NIL, NIL, 344567, 164700000);
注意:此處要用NIL而不是NULL,否則,前三個字段會被設為空。
<b>6.8 事 件</b>
TDataSet的事件主要分為兩大類,一類是Before系列,另一類是After系列,清單如下:
.BeforeOpen,AfterOpen發生在資料集打開前後;
.BeforeClose,AfterClose發生在資料集關閉前後;
.BeforeInsert,AfterInsert發生在插入了一條新的記錄前後;
.BeforeEdit,AfterEdit 發生在進入dsEdit狀态前後;
.BeforePost,AfterPost 發生在寫資料集的前後;
.BeforeCancel,AfterCancel發生在取消修改的前後;
.BeforeDelete,AfterDelete發生在删除記錄的前後。
此外,當資料集中增加了一條新的記錄時就會觸發OnNewRecord事件,當“計算字段”的值需要重算時将觸發OnCalcFields事件。
Before系列的事件常常用來中止操作。例如,當調用Delete函數試圖删除目前記錄時,在目前記錄将要删除前會觸發BeforeDelete事件,可以在處理BeforeDelete事件的句柄中調用Abort或觸發一個異常放棄删除目前記錄,程式示例如下:
Pocedure TForm1.TableBeforeDelete (Dataset: TDataset)
If MessageDlg('Delete This Record?', mtConfirmation, mbYesNoCancel, 0) <> mrYes Then Abort;
After系列的事件往往用來在狀态欄上通知使用者,程式示例如下:
Procedure TForm1.Table1AfterDelete(DataSet: TDataSet);
StatusBar1.SimpleText := Format('有%d 條記錄',[DataSet.RecordCount]);
OnCalcFields事件主要用于給出“計算字段”的值。AutoCalcFields屬性的值決定了什麼時候會發生OnCalcFields事件。
如果AutoCalcFields屬性設為True,下列情況下會發生OnCalcFields事件:
.資料集被打開時;
.在資料控件中,輸入焦點從一條記錄移到另一條記錄;
.在資料控件中,輸入焦點從一個字段移到另一個字段;
.目前記錄被修改或從資料庫中檢索了一條記錄。
不過,即使AutoCalcFields屬性設為False,當資料集中的任意一個非計算字段的值發生變化時都會觸發OnCalcFields事件。
由于OnCalcFields事件有可能是頻繁發生的,是以,處理OnCalcFields 事件的代碼要盡可能地簡短。在AutoCalcFields屬性設為True的情況下,在處理OnCalcFields事件的句柄中不能修改資料集的資料,因為一旦目前記錄被修改,又要觸發OnCalcFields事件,進而導緻無限循環。例如,假設您在處理OnCalcFields事件的句柄中調用了Post,就會觸發OnCalcFields事件,導緻再次調用Post,再次觸發OnCalcFields事件……
<b>6.9 TBDEDataSet</b>
TBDEDataSet是從TDataSet繼承下來的,它提供了通過BDE(BorlandDatabase Engine)通路資料的能力。這一節主要介紹TBDEDataSet,讀者應當對前面介紹的TDataSet已經有了比較深刻的認識。
與TDataSet一樣,TBDEDataSet也是虛拟的和抽象的,除非您想建立自定義的資料集,否則,一般不需要直接用到TBDEDataSet。
TBDEDataSet重載了TDataSet中涉及記錄導航、索引和書簽的方法,增加了一些處理BLOB字段、緩存更新的屬性、方法和事件。
<b>6.9.1 CacheBlobs屬性</b>
TBDEDataSet的CacheBlobs屬性用于控制BDE是否把BLOB字段的内容放到緩存中。如果這個屬性設為True,當應用程式讀取BLOB字段的值時,BDE将把BLOB字段的内容放在緩存中,這樣,當應用程式下次要讀取這個字段的值時,就不必再從資料庫伺服器那兒去檢索,隻要直接從記憶體中取過來就行了,這樣可以提高應用程式的性能。
不過,如果應用程式需要頻繁地更新BLOB字段的值,這時候反而應當把CacheBlobs屬性設為False,這樣能保證檢索到的BLOB字段的值總是最新的。
<b>6.9.2 緩存更新</b>
TBDEDataSet提供了緩存更新的技術。所謂緩存更新就是,應用程式從資料庫中檢索資料,在本地緩存中建立一個副本,使用者對資料進行修改後,也隻是反映在緩存中,以後可以調用ApplyUpdates一次性地把所有的修改反映到資料集中。
可以看出,緩存更新技術可以明顯地提高應用程式的性能,而且可以友善地取消修改,隻要還沒有調用ApplyUpdates。下面列出了TBDEDataSet中有關緩存更新的屬性、方法和事件:
.CachedUpdates如果這個屬性設為True,緩存更新有效;
.UpdateObject用于指定一個TUpdateSQL構件來更新基于查詢的資料集;
.UpdatePending如果緩存中有未決的記錄,這個屬性就傳回True;
.UpdateRecordTypes指定資料集中哪些記錄是可見的;
.UpdateStatus傳回目前的更新狀态;
.OnUpdateError如果更新過程中出錯将觸發這個事件;
.OnUpdateRecord每更新一條記錄就會觸發一次這個事件;
.ApplyUpdates把緩存中的資料寫到資料集中;
.CancelUpdates把緩存中未決的修改取消;
.CommitUpdates把緩存清掉;l FetchAll從資料庫檢索所有記錄到緩存中;
.RevertRecord撤消對目前記錄的修改。
<b>6.10 TDBDataSet</b>
TDBDataSet是從TBDEDataSet繼承下來的,它提供了資料庫和會話期管理的能力。
TDBDataSet中增加了若幹個屬性和方法用于管理資料庫和BDE會話期,包括:
.CheckOpen檢查資料庫是否已打開;
.Database傳回一個TDatabase構件;
.DBHandle傳回一個BDE句柄,調用BDE的API時要用到這個句柄;
.DBLocale傳回目前的國際語言驅動程式;
.DBSession傳回一個BDE會話期對象;
.DatabaseName用于指定要通路的資料庫;
.SessionName用于指定一個BDE會話期對象。
這裡詳細解釋一下DatabaseName屬性和SessionName屬性。如果應用程式要通路遠端資料庫伺服器如Sybase、Oracle或InterBase,應當用TDatabase構件來連接配接資料庫,此時,應當設定DatabaseName屬性指定要連接配接的資料庫,可以設為TDatabase構件的名稱。如果沒有顯式地使用TDatabase構件,DatabaseName屬性應當設為BDE 别名。對于Paradox和dBASE表來說,可以設為表的路徑。
SessionName屬性用于指定一個BDE會話期對象。如果應用程式沒有顯式地使用TSession構件,不必設定這個屬性。如果應用程式顯式地使用了多個TSession構件,應當設定SessionName屬性指定其中一個。
一般來說,應用程式用不到DBHandle、DBLocale和DBSession等屬性,除非要直接調用BDE的API。這三個屬性都是隻讀的。
TDBDataSet中還有一個隻讀的Provider屬性,它能夠傳回一個IProvider接口。在多層的Client/Server應用程式中,客戶程式需要通過IProvider接口與應用伺服器通訊。