2012-08-03

【Delphi】ClientDataSet 應用(2):master-detail 架構 + autoInc field

假設我們今天要寫一個訂單程式, 需要 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 前後的變化.

沒有留言:

張貼留言