2010/03/16

哼…TDataSetProvider…真是被你打敗了呀

不知道是不是BCB6過於老舊,對ClientDataSet做Edit工作(SQL's Update)
一直很不順利,老是出現:
"Table unkown 'TableName'"

於是丟了個SQLMonitor去除錯
結果從SQLMonitor查到的語法是這樣的:
update "TableName"
set 'Field' = ?
where
set 'Field' = ?

看起來沒錯?錯了!
在Firebird 2.1下,tablename是不行有任何引號的。
所以,問題就這樣出來了。

可是以前怎麼沒遇到?
原來以前都是自己下SQL語法,難怪都沒發生問題。
現在要偷懶叫tDataSetProvider來處理卻問題一堆。

DataSnap你好樣的…

最後,再附上以前的前輩是怎麼處理這個問題的。

來源:更新多個資料表的方法

更新多個資料表的方法
問題描述

使用 Delphi5 + BDE + MIDAS

我希望能一次更新兩個以上的資料表,可是這些異動必須在同一個交易中,
也就是其中任何一個資料表發生錯誤時必須全部rollback,應該怎麼做?

典型的場景:

假設有一個訂單管理程式,當新增一筆訂單時需要同時更新客戶資料與訂單資料。
一般的做法是在 DataSetProvider 的 BeforeUpdateRecord 事件中加入更新其他資料表的程式碼,例如:

1
2
3
4
5
6
7
8
procedure TForm1.dspOrdersBeforeUpdateRecord(Sender: TObject; SourceDS: TDataSet; DeltaDS: TClientDataSet; UpdateKind: TUpdateKind; var Applied: Boolean);
begin
  if UpdateKind = ukModify then
  begin
    qryUpdate.SQL.Text := 'update Customer set City="AAA" where CustNo="1221"';
    qryUpdate.ExecSQL;
  end;
end;

以上面的例子來看,會先更新客戶資料 Customer,然後再更新訂單資料 Orders。
可是如果更新 Orders 時發生了錯誤,之前對於 Customer所做的異動卻無法 rollback 了。
即使這兩個資料集元件都連結到相同的 TDatabase 元件也一樣。
這樣一來仍然無法達到資料完整性。

解決方案 A:

使用 Dan Miser 的 MIDAS Essensial Pack。使用時需 uses CDSUtil
單元,程式碼可以參考下面的範例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
//-----App server 端:-----
procedure TMtsDmod.MyApplyUpdates(var vDeltaArray: OleVariant; vProviderArray: OleVariant);
begin
try
CDSApplyUpdates(vDeltaArray, vProviderArray);
SetComplete;
except
SetAbort;
end;
end;
 
//-----Client 端:-----
procedure TForm1.btnApplyUpdatesClick(Sender: TObject);
var
cdsArray: array [0..1] of TCLientDataSet;
vDeltaArray: OleVariant;
vProviderArray: OleVariant;
begin
cdsArray[0] := cdsPubs;     // Master
cdsArray[1] := cdsTitles;   // Detail
vDeltaArray := RetrieveDeltas(cdsArray);
vProviderArray := RetrieveProviders(cdsArray);
DCOMConnection1.AppServer.ApplyUpdates(vDeltaArray, vProviderArray);
ReconcileDeltas(cdsArray, vDeltaArray);
end;


解決方案 B:

不要讓 DataSetProvider 更新資料,讓其連接的資料集元件自行更新資料。
首先, DataSetProvider 的 ResolveToDataSet 必須設為 True。
然後在 qryOrders 的 OnUpdateRecord 事件中撰寫更新其他資料表的程式碼,像這樣:
1
2
3
4
5
6
7
8
procedure TForm1.qryOrdersUpdateRecord(DataSet: TDataSet; UpdateKind: TUpdateKind; var UpdateAction: TUpdateAction);
begin
if UpdateKind = ukModify then
begin
qryUpdate.SQL.Text := 'update Customer set City="AAA" where CustNo="1221"';
qryUpdate.ExecSQL;
end;
end;


你可以故意讓 Orders 在更新時發生錯誤,
看看 Customer 資料表中 CustNo 為 '1221' 的那筆記錄的 City 欄位是否被改為 'AAA',
如果不是的話就表示這個方法的確可行,你可以自行驗證看看。


解決方案 C:

在 dataset provider 的 BeforeUpdateRecord 事件中自行處理掉所有
的更新的工作。例如:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
procedure TForm1.dspOrdersBeforeUpdateRecord(Sender: TObject; SourceDS: TDataSet; DeltaDS: TClientDataSet; UpdateKind: TUpdateKind; var Applied: Boolean);
begin
qryCustomer.UpdateObject := usqlCustomer;
usqlCustomer.SetParams(UpdateKind);
usqlCustomer.ExecSQL(UpdateKind);
 
 
qryOrders.UpdateObject := usqlOrders;
usqlOrders.SetParams(UpdateKind);
usqlOrders.ExecSQL(UpdateKind);
 
 
Applied := True;
end;


使用這個方法時別忘了最後要將 Applied 設為 True,
告訴 dataset provider 你已經完成更新資料的工作。

2010/04/01 更新
我用了實體SQLDataSet+provider+clientdataset又重試了一下之前的問題
發現又正常了
運作的內容是:
1
2
3
4
update TableName  set
Field = ?
where
Field = ?

沒有留言:

張貼留言