2009年8月12日 星期三

ADO 與MTS的進一步搭配

很抱歉,因年代久遠,原出處已不可考,若有人知道原文出處,再麻煩通知我

這回討論一下ADO Recordset的MarshalOptions、UpdateBatch、CancelBatch 、Filter 、Resync 與 Field 物件中 Value、OriginalValue、UnderlyingValue 在三層式架構中的應用。看完這文章應該可以知道ADO為什麼才是三層架構中Data Access的主力而不是RDO/DAO。

我們先前討論過離線的ADODB.Recordset,它的特性是不必有DataBase Connection的連線而可以在Local端保有Recordset,而且在Local端保有的是整個Recordset而不是MTS上某個recordset的Reference(透過Marshal的技術)。

做到了上面的事,其實只做完Query的動作,如果傳回的Recordset要做Insert/Update/Delete呢?當然了,我們可以用SQL指令將之完成,另外也可以用這Off Line的Recordset來做,而且這裡面有許多很不錯的技術。
我們可以對OffLine的Record做AddNew、Delete、Update,但是這不會立即改變後端的資料庫之Data(這一定的嘛),要等到我們重新建立連線(set rs.ActiveConnection = cn)後,使用UpdateBatch來做批次更新。但是更新的過程中有可能發現原先的資料被他人修改過了,這時,UpdateBatch會產生Run Time Error,我們可以catch這個error,之後,rs.Filter = adFilterConflictingRecords 令之只取得產生衝突的Record rs.Resync adAffectGroup, adResyncUnderlyingValues 取得衝突者的UnderlyingValues Field物件有三個值
.Value 我們程式更動後的值
.OriginalValue Select 指令由database中取出的值(最原始的值)
.UnderlyingValue 就是資料庫內當時最新的值。它在剛Select出時,其值和 OriginalValue相同,要在使用 Resync方法(第二個參數傳adResyncUnderlyingValues)才會取得最新的值。

取出這些值後便可以依實際的情況決定有問題者的解決之道,而UpdateBatch是如何決定誰有問題誰沒有問題呢?其實它是由 UnderlyingValue與Database內的值二者做一比較,相同者便OK,不同便是有Error,所以上面的例子中:
rs.Resync adAffectGroup, adResyncUnderlyingValues 會取得所有有衝突者的UnderlyingValue
如果下的是
rs.Resync adAffectCurrent, adResyncUnderlyingValues 則只取回current Record的值
因此,我們可以視情況再下一次UpdateBatch,因為UnderlyingValue已更新過,所以和Database中的值應相同(除非這時又被他人更動了)。

而使用 rs.MarshalOptions = adMarshalModifiedOnly 會使得Client端傳送給MTS 上ActiveX Server 的Recordset參數,只傳更動過者,而不是全部(例如:rs內原本有500筆資料,但我們更動了其中3筆,若要透過ActiveX Server的Method來幫我們更動這些資料時,一定得把recordset當作參數傳過去,要傳之前有設adMarshalModifiedOnly,就只傳三筆過去,否則會傳500筆)

我們討論這些時是以以下的假設:我們的ActiveX.Dll Server安裝於MTS中,且MTS是於獨立的機器上,Client透過DCOM的方式呼叫該MTS上的Dll Server做事,而SQL Server是7.0版,且又另外獨立於另一部機器。(也就是說至少用了三部PC,一台MTS,一台SQL7,一台一般的Client。其實,可以不用這麼多,就算同在一部時也可以做啦。)

以下是幾個設計上的重點:
1. CursorLocation = adUseClient ,且CursorType = adOpenStatic LockType = adLockBatchOptimistic 的方式開啟Recordset
Set cn = New ADODB.Connection
connstr = "Data Source=cwwnt2;User ID=sa;Password=;Initial Catalog=cwwtest"
cn.Provider = "SQLOLEDB"
cn.ConnectionString = connstr
cn.Open
Set rs = New ADODB.Recordset
rs.CursorLocation = adUseClient
Set rs.ActiveConnection = cn
rs.Open strSQL, cn, adOpenStatic, adLockBatchOptimistic
2. 在connection close之前設定 rs.ActiveConnection = Nothing來變成離線Recordset
Set rs.ActiveConnection = Nothing
cn.Close
Set cn = Nothing
3. 把該RecordSet傳回 4. 於Client中Update/Insert/Delete資料,等我們覺得可以Update到DataBase時,需再度使offline的Recordset變On-Line,變OnLine的方式如下:
Set cn = New ADODB.Connection
connstr = "Data Source=cwwnt2;User ID=sa;Password=;Initial Catalog=cwwtest"
cn.Provider = "SQLOLEDB"
cn.ConnectionString = connstr
cn.Open
Set rs.ActiveConnection = cn '就是這一行令之變回online
On Line後使用Recordset的UpdateBatch來啟動批次更新或CancelBatch取消更新。不過這裡有個問題,要在Client端做Online的動作還是MTS端,應都可以,本例是在MTS端,因這樣做時,有其他的技巧。 5. 把Off Line的Recordset當作參數傳給MTS上的ActiveX Server。這裡又有學問了,傳給MTS 上 ActiveX Server的Recordset也是透過Marshal的技術來做,所以不是只傳Reference過去,而是整個Recordset傳過去。然而,有時候並不必把所有的Record都傳過去,只要傳有修改過的便可以,於是我們可以設定 Recordset 上MarshalOptions = adMarshalModifiedOnly,如此一來傳過去的資料就會只有更動過者,特別做個說明,這只有在MTS上才會如此,如果同一個Process內設定MarshalOptions,而傳Recordset時,並不會有作用(因為是傳Reference過去而已)
rs!nckm_dept = "pp"
rs.Update
rs.Move 3
rs!nckm_dept = "pp"
rs.Update
rs.MarshalOptions = adMarshalModifiedOnly
Set aa = CreateObject("OffADO.MyClass1")
i = aa.SubmitUpdate(rs) '如此SubmitUpdate()只會收到兩筆Record
6.MTS中ActiveX Server的SubmitUpdate方法中下UpdateBatch,並攔截產生衝突時的錯誤
On Error Resume Next
rs.UpdateBatch
If Err.Number <> 0 Then
Err.Clear
'rs.Filter = adFilterConflictingRecords
'rs.Resync adAffectGroup, adResyncUnderlyingValues
'做一些企規則中應做的處理程式
Set rs.ActiveConnection = Nothing '如此又使rs變offline才能傳回去
SubmitUpdate = False
cn.Close
else
cn.Close
SubmitUpdate = True
End If
7.如上面程式所示,如果rs要再傳回client,那麼,還得設它成off line的Recordset不過,這裡有一點十分重要,傳回去的Recordset沒有傳UnderlyingValue回去(我的testing中就算在Client端做resync也取不出UnderlyingValue),也就是說如果要傳回Client端處理,還得用其他的Recordset存UnderlyingValue來做。 以下在OffADO.MyClass1 (專案名:OffADO Class名:MyClass)
Option Explicit
Implements ObjectControl
Private mStr5 As String

Public Function GetRecordset(ByVal strSQL) As ADODB.Recordset
Dim cn As ADODB.Connection
Dim rs As ADODB.Recordset
Dim connstr As String, ct As ObjectContext

On Error GoTo Errh
Set ct = GetObjectContext()
Set cn = New ADODB.Connection
connstr = "Data Source=cwwnt2;User ID=sa;Password=;Initial Catalog=cwwtest"
cn.Provider = "SQLOLEDB"
cn.ConnectionString = connstr
cn.Open

Set rs = New ADODB.Recordset
rs.CursorLocation = adUseClient

Set rs.ActiveConnection = cn
rs.Open strSQL, cn, adOpenStatic, adLockBatchOptimistic

Set rs.ActiveConnection = Nothing '如此rs 變成Off line的Recordset
cn.Close '雖close,但不會把rs也close掉

Set cn = Nothing
Set GetRecordset = rs '將Recordset傳回去,此時傳回的不只是Reference

ct.SetComplete

Exit Function

Errh:
GetObjectContext.SetAbort
Set GetRecordset = Nothing

End Function

Private Sub ObjectControl_Activate()
End Sub

Private Function ObjectControl_CanBePooled() As Boolean
ObjectControl_CanBePooled = True
End Function

Private Sub ObjectControl_Deactivate()
End Sub

Public Function SubmitUpdate(rs As ADODB.Recordset) As Boolean
Dim cn As ADODB.Connection
Dim connstr As String, ct As ObjectContext

'On Error GoTo Errh

Set ct = GetObjectContext()
Set cn = New ADODB.Connection

'connstr = "Data Source=OPEN_VIEW;User=cww;Password=111;Initial Catalog=cwwtest"
connstr = "Data Source=cwwnt2;User ID=sa;Password=;Initial Catalog=cwwtest"

cn.Provider = "SQLOLEDB"
cn.ConnectionString = connstr

cn.Open
Set rs.ActiveConnection = cn

'----以下純是為了證實 .MarshalOptions = adMarshalModifiedOnly的作用

Dim fn As Integer
fn = FreeFile
Open "c:\tt.txt" For Append As fn
Print #fn, rs.RecordCount '如此可以看到tt.txt中記錄傳入的筆數
Close fn
'-----------------------------------------------------------------

On Error Resume Next

rs.UpdateBatch
If Err.Number <> 0 Then

Err.Clear

'rs.Filter = adFilterConflictingRecords
'rs.Resync adAffectGroup, adResyncUnderlyingValues

'做一些企規則中應做的處理程式
Set rs.ActiveConnection = Nothing '再變成off line才能傳回去
SubmitUpdate = False
cn.Close
ct.SetAbort
Else
cn.Close
SubmitUpdate = True
ct.SetComplete
End If
End Function
以下在client中呼叫遠端的ActiveX Server
Dim aa As OffADO.MyClass1

Private Sub Command1_Click()

Dim strSQL As String, SubmitOK
Dim rs As ADODB.Recordset

Set aa = CreateObject("OffADO.MyClass1") '最好不要用New
strSQL = "Select * from qppfa where case_no='E8701761' and kind = 'LS'"

Set rs = aa.GetRecordset(strSQL)
Set aa = Nothing '最好立即設為Nothing,否則遠端Process不會結束

If Not rs Is Nothing Then

rs.Delete
rs.Move 3
rs!nckm_dept = "pp"
rs.Update
rs.MarshalOptions = adMarshalModifiedOnly

Set aa = CreateObject("OffADO.MyClass1")
SubmitOK = aa.SubmitUpdate(rs)

If Not SubmitOK Then
'Do Something
End If

End If

Set aa = Nothing
End Sub

沒有留言:

張貼留言