假設我們今天要寫一個訂單程式, 需要 2 個 table, 主檔(master) 名為 sell, 明細檔(detail) 名為
selld.
先在 database 上建 2 個 table, 請依據您所使用的 database 品牌自行調整.
sell
的欄位有:
sellno VARCHAR(10) - 訂單編號, 且為主鍵(primary key),
sellsum INTEGER -
訂單總計.
selld 的欄位有:
sellno VARCHAR(10),
pkey 為自動加值欄位(在 Postgres
的欄位型態為 SERIAL),
prodno VARCHAR(5) - 商品代碼,
selld 的主鍵因各人觀點會有不同做法, 有的人直接以
pkey 做為主鍵,
在本文裡我將 sellno 及 pkey 併為主鍵,
當 detail 的主鍵含有與
master 主鍵相同的欄位, 在新增明細記錄時, Delphi 會自動帶入主檔(master)
的主鍵值.
文後會說明
sell 與 selld 間以 sellno
做關聯
=====================
啟動 Delphi
先建 master
在 Form1
上放一個 ADOConnection, 一個 ADOQuery, 一個 DataSetProvider, 一個 ClientDataSet, 一個
DataSource 及 一個 DBGrid.
為了解說方便, 將元件的 name 稍做變動, 可參考下圖
ADOConnection1
--> conn
ADOQuery1 --> tblM
DataSetProvider1 -->
dspM
ClientDataSet1 --> cdsM
DataSource1 --> dsM
DBGrid1 -->
grdM
設定元件屬性
conn.Connected := false;
tblM.Connection :=
conn;
tblM.SQL.Text := 'SELECT * FROM sell';
dspM.DataSet :=
tblM;
cdsM.ProviderName := dspM;
dsM.DataSet := cdsM;
grdM.DataSource
:= dsM;
此時將 cdsM.Active 變為 true, conn.Connected 屬性也會變成 true; 另外, 看到即使
tblM.Active 屬性是 false, 仍會看到 grdM 變為可看到資料的狀態.
接下來,
建 detail放二個 DataSource, 並將其中一個移至 tblM 下方, 一個 ADOTable, 一個 DataSetProvider,
一個 ClientDataSet 及 一個 DBGrid.
靠近 tblM 的 DataSource 更名為
dsMLink
ADOTable1 --> tblD
DataSetProvider1 -->
dspD
ClientDataSet1 --> cdsD
DataSource1 --> dsD
DBGrid1 -->
grdD
設定元件屬性
dsMlink.DataSet := tblM;
tblD.Connection :=
conn;
tblD.TableName := selld;
tblD.MasterSource := dsMlink;
進入
tblD.MasterFields, 依下圖設定
tblD.MasterFields
:= sellno;
tblD.IndexFields := sellno;
dspD.DataSet :=
tblD;
cdsD.ProviderName := dspD;
dsD.DataSet := cdsD;
grdD.DataSource
:= dsD;
回到 cdsM, 打開 Fields Editor, 選加入全部欄位, 會發現 Delphi 自動帶入一個欄位 其型別為
TDataSet, 我們將藉由它自動連結 tblD
再稍微修改
dspM 的屬性:
dspM.Options 的 CascadeUpdate & CascadeDelete 設為 true, 這裡一定要設定,
不然主檔有異動(更新/刪除) 時, 無法一併異動明細檔.
然後再打開 cdsD 的 Fields Editor, 也是加入所有欄位,
同時從頭巡一遍 cdsM 及 cdsD 的 Fields Editor 內的全部欄位的 ReadOnly 屬性是否被變更為 true,
請依照您實際需要調整各欄位的 ReadOnly 屬性, 注意主檔與明細檔的關聯欄位 及
AutoInc 欄位必不可為 ReadOnly.
cdsD.DataSetField := cdsMtblD;
這個步驟得在 cdsM 在
Fields Editor 加入了那個 TDataSetField 型別的欄位才有辦法設定.
同時檢查
cdsD.ProviderName 設定是否跑掉變空白(有時會發生)
到此, 將 conn.Connected 屬性變為 false, 再將
cdsM.Active 變為 true, 就可以看到整個以 ClientDataSet 架構的 master-detail 程式已
ready.
而且您會發現在
grdM 的欄位有個 tblD, 其每筆記錄(每列)的值為 (DATASET). 如果主檔該筆記錄底下沒有相關聯的明細檔記錄, 則會以小寫 (DataSet)
顯示.
接下來, 要寫一點點程式
定義一個全域變數
var
Form1: TForm1;
sid:
integer;
implementation
{$R *.dfm}
還有
cdsM.OnNewRecord
procedure
TForm1.cdsMNewRecord(DataSet: TDataSet);
begin
sid :=
-1;
end;
還有
cdsD.OnNewRecord
procedure
TForm1.cdsDNewRecord(DataSet: TDataSet);
begin
DataSet.FieldByName('pkey').AsInteger := sid;
Dec(sid);
end;
然後執行程式,
先在 grdM 輸入 sellno, 然後到 grdD 輸入 prodno,
這時候 grdD 會自動帶出 sellno 及 pkey 的值,
如下圖:
但是如果當初您在 create
table 時, 明細檔 selld 的 sellno 並未含在主鍵(primary key) 內, 則 sellno
不會自動帶出.
再在 grdD 新增幾筆記錄, pkey 值會一直遞減, 當主檔及明細檔都儲存確認後, 您可以任意瀏覽 grdM
內的記錄, 下面的 grdD 會自動顯示對應該筆主檔的明細檔資料.
現在試著關閉程式, 再啟動程式, 您會發現剛剛新增的主檔,
明細檔的記錄都不見了!!
這是因為 ClientDataSet 並不會把在 client 端,
也就是使用者執行前端程式操作的電腦的資料異動(新增/修改/刪除) 立即上傳至後端 database.
要實際上傳存回 database, 需要
ApplyUpdates.
現在再加入 2 個 button,
button1 更名為 btnApply,
btnApply.Caption := 'apply update';
button2 更名為 btnRefresh,
btnRefresh.Caption := 'refresh';
procedure
TForm1.btnApplyClick(Sender: TObject);
begin
ShowMessage('apply update-->'+IntToStr(cdsM.ApplyUpdates(0)));
end;
procedure
TForm1.btnRefreshClick(Sender: TObject);
begin
cdsM.Refresh;
end;
ApplyUpdates
及 Refresh 的詳細用法, 請參考 Help.
cdsM.ApplyUpdates(0) 會傳回錯誤數量, 傳回數字 0 則表示整個上傳沒有錯誤.
而且只要 cdsM 做 ApplyUpdates 就好了, Delphi 會一併把明細檔也上傳至 database.
cdsM.Refresh
則會將實際存在 database 的值傳至 client 端更新,
所以您可以觀察 明細檔的 pkey 值, 在 refresh 前後的變化.
沒有留言:
張貼留言