多層應用中的事務處理,是必須的,如果處理不好,就會出現各種資料不同步的現象,無法投入使用。以前用ASTA實作的多層應用,是在用戶端利用ASTA機 制,将要送出的資料集、執行的SQL及SP,統統生成到一個腳本中,在ASTA是TAstaParamList,然後一次性送出到伺服器,在伺服器端在執 行這個腳本時,開啟事務,執行腳本,如果成功則Commit,失敗則Rollback。
kbmMW提供了更好的事務處理機制,即可以在伺服器端做事務處理(TkbmMWTransactionResolve)也可以在用戶端處理(TkbmMWClientTransactionResolve)。
在用戶端,隻要利用kbmMWClientTransactionResolve.Resolve(Query1,Query2,...)即可實作事務控制,此方法傳回True,表明事務送出成功。如果送出失敗,伺服器端自動Rollback,完成事務復原。
有幾個事件可以捕獲送出失敗資訊。
1.kbmMWClientQuery.OnResolveError事件
2.kbmMWClientTransactionResolve.onResolveError事件
此外,當kbmMWClientQuery在kbmMWClientTransactionResolve提時失敗時,kbmMWClientTransactionResolve會為kbmMWClientQuery生成具體的錯誤記錄的資訊,儲存在kbmMWClientQuery屬性中,該屬性是kbmMWMemTable類型。
利用這幾種方式,可以很好的處理事務失敗後的錯誤提示等進一步處理。
問題是當送出失賬時,會産生新的問題:
1.假設有T1,T2兩個實體表,需要使用者同時修改并送出
2.當使用者對T1,T2都做了修改後,用kbmMWClientTransactionResolve.Resolve(T1,T2)送出給伺服器,當t2送出失敗時,比如主鍵重複,問題就來了。
3.如果使用者修改了T2的主鍵重複錯誤,再次送出,這時,不會再産生送出錯誤。
4.重新打開T1,T2,會發現T1的修改丢失,而T2正常修改了。
這種情況,對使用者的操作過程來講,是大問題,明明改了業務資料,儲存時産生錯誤,按錯誤提示修正了錯誤資料,再儲存,系統儲存成功,再次調出這筆業務資料,使用者會發現,修改結果沒有被正确的儲存!
對開發者來說,就是要在事務送出失敗後,能恢複失敗前的現場,即要把T1,T2的Delta儲存起來,不能因為送出失敗,而毀掉T1,T2的修改!
試圖查找kbmMW提供現成的機制(其實,感覺不應該發生這樣的問題),沒有找到。于是想一個方法,就是Resolve前将T1,T2儲存起來,送出有錯誤再恢複。
用AllData方法不行:
代碼:
在BeforeResolve事件儲存
procedure TForm2.kbmMWClientTransactionResolver1BeforeResolve(Sender: TObject;
const ADatasets: TkbmMWClientCursorArray);
begin
v1:=kbmMWClientQuery1.AllData;
v2:=kbmMWClientQuery2.AllData;
end;
//送出不成功,試圖恢複(這裡隻是測試,正常應恢複沒有出錯的Query,好能再一次送出)
procedure TForm2.Button2Click(Sender: TObject);
if not
kbmMWClientTransactionResolver1.Resolve([kbmMWClientQuery1,kbmMWClientQuery2,kbmMWClientQuery3])
then begin
kbmMWClientQuery1.AllData:=v1;//這句出錯,提示非法的屬性值
kbmMWClientQuery2.AllData:=v2;
end;
還有一種方法,就是把delta寫入流,送出失敗再恢複,不知是否可行?先放一下。
問題一定出在TkbmMWClientTransactionResolver.Resolve方法中,硬着頭皮跟蹤一下,咱這功力看大俠的套路,心驚膽顫,運氣還不錯,終于查到,問題出ProcessErrorTable方法中:
procedure TkbmMWCustomPooledCursor.ProcessErrorTable
在這個方法中,這裡做了CheckPoint,把Delta幹掉了!當送出的資料集沒有傳回ErrorTalbe時,系統認為這個資料集正确送出,是以做了CheckPoint。
// Check if no errortable defined.
if (FErrorTable.FieldCount=0) or
(FErrorTable.RecordCount<=0) then
CheckPoint;
exit;
原因找到了,一時間還想不出最好的修改方法來解決這個問題。試圖找到一個屬性來控制這種行為,也沒有找到。等xalion來幫助了!
和xalion讨論該問題,也沒有找到kbmMW已有的機制,對于上面提到的問題點,也可以确定。
再進一步分析一下ClientTransactionResolve.Resolve方法的執行過程,大體上分三步:
1.對送出的資料集,制作成Variant數組及Stream
2.将1生成的内容一次性送出到伺服器端并接收傳回的結果
3.利用傳回結果對每一個送出的資料集進行ErrorTable處理,問題就出在這一步上。
首先,kbmMW從傳回的結果,生成每個資料集的ErrorTable内容,然後根據ErrorTable,調用ProcessErrorTable方
法,ProcessErrorTable方法有兩個主要目的:如果無錯,則調用CheckPoint,取消這次提前的修改内容,有錯,不調用
CheckPoint,觸發OnResolveError事件。
對于一個資料集是否要Checkpoint,不應該取決于目前資料集是否有錯,而應該由本次提要的結果來決定,就是說:當事務成功,則每一個資料集都應該做Checkpoint,事務不成功,則對于無錯的資料集也不能Checkpoint!
按此想法,對ClientTransactionResolve.Resolve做了修改,測試上面遇到的問題,可以正确儲存使用者的修改。
修改方法是注冊掉原ProcessErrorTable的調用,然後增加下面的代碼,核心那行,我加黑了!
cnt:=0;
for i:=low(ADatasets) to high(ADatasets) do
with ADatasets[i] do
if not IsDataModified then continue;
v:=vResult[cnt];
inc(cnt);
if VarIsNull(v) then continue;
if VarArrayHighBound(v,1)>2 then
sFlags:=v[3]
else
sFlags:='';
NoCheckpointOnError:=(pos('NOCHECKPOINTONERROR',sFlags)>0);
DisableMasterDetail;
try
bm:=GetBookmark;
//如果整體事務成功或者目前表有錯誤
if Result or (ErrorTable.RecordCount>0) then
ProcessErrorTable(TransactionOperation<>mwtoResolve,NoCheckpointOnError);
finally
GotoBookmark(bm);
except
FreeBookmark(bm);
EnableMasterDetail;