競合の検出および解決
即時モードで Recordset を扱う場合、コンカレンシーの問題が発生する可能性はかなり低くなります。 一方、アプリケーションにバッチ モード更新を使っている場合、あるユーザーがレコードを変更したときが、同じレコードを編集している他のユーザーが変更を保存する前である可能性が高くなります。 このような場合、アプリケーションで競合を適切に処理することをお勧めします。 最後にサーバーに更新を送信した個人を "勝ち" にすることができます。また、競合する 2 つの値の選択肢を提示し、最新のユーザーがどちらの更新を優先するかを判断できるようにすることもできます。
いずれの場合でも、ADO には、この種の競合を処理する Field オブジェクトの UnderlyingValue および OriginalValue プロパティがあります。 これらのプロパティは、Recordset の Resync メソッドと Filter プロパティと組み合わせて使います。
注釈
バッチ更新中に ADO で競合が発生すると、Errors コレクションに警告が追加されます。 そのため、BatchUpdate を呼び出した直後に必ずエラーを確認し、エラーが見つかったら、競合が発生したという前提でテストを開始してください。 最初の手順は、Recordset の Filter プロパティを adFilterConflictingRecords に設定することです。 これにより、Recordset のビューは、競合しているレコードのみに制限されます。 この手順の後、RecordCount プロパティが 0 になった場合、エラーは競合以外の原因で発生したことがわかります。
BatchUpdate を呼び出すと、ADO とプロバイダーにより、データ ソースに対して更新を実行する SQL ステートメントが生成されます。 一部のデータ ソースには、WHERE 句で使用できる列の種類に制限があることに注意してください。
次に、AffectRecords 引数を adAffectGroup に設定し、ResyncValues 引数を adResyncUnderlyingValues に設定して Resync メソッドを Recordset に対して呼び出します。 Resync メソッドを使って、基となるデータベースの現在の Recordset オブジェクトに含まれるデータを更新します。 adAffectGroup を使うと、現在のフィルター設定で表示されるレコードのみ、つまり競合するレコードのみを確実にデータベースと再同期することができます。 その結果、大規模な Recordset を扱う場合のパフォーマンスに大きな違いが出る可能性があります。 Resync を呼び出すときに ResyncValues 引数を adResyncUnderlyingValues に設定すると、UnderlyingValue プロパティにデータベースの (競合する) 値を含めること、Value プロパティにユーザーが入力した値を保持すること、OriginalValue プロパティにそのフィールドの元の値 (最後に成功した UpdateBatch 呼び出しが行われる前の値) を保持することを確実に行うことができます。 その後、これらの値を使ってプログラムで競合を解決することや、使う値を選ぶようにユーザーに要求することができます。
この手法を次のコード例で示します。 この例では、UpdateBatch が呼び出される前に、別の Recordset を使って基となるテーブルの値を変更することにより、人為的に競合を作成しています。
'BeginConflicts
strConn = "Provider=SQLOLEDB;Initial Catalog=Northwind;" & _
"Data Source=MySQLServer;Integrated Security=SSPI;"
strSQL = "SELECT * FROM Shippers WHERE ShipperID = 2"
'Open Rs and change a value
Set objRs1 = New ADODB.Recordset
Set objRs2 = New ADODB.Recordset
objRs1.CursorLocation = adUseClient
objRs1.Open strSQL, strConn, adOpenStatic, adLockBatchOptimistic, adCmdText
objRs1("Phone") = "(111) 555-1111"
'Introduce a conflict at the db...
objRs2.Open strSQL, strConn, adOpenKeyset, adLockOptimistic, adCmdText
objRs2("Phone") = "(999) 555-9999"
objRs2.Update
objRs2.Close
Set objRs2 = Nothing
On Error Resume Next
objRs1.UpdateBatch
If objRs1.ActiveConnection.Errors.Count <> 0 Then
Dim intConflicts As Integer
intConflicts = 0
objRs1.Filter = adFilterConflictingRecords
intConflicts = objRs1.RecordCount
'Resync so we can see the UnderlyingValue and offer user a choice.
'This sample only displays all three values and resets to original.
objRs1.Resync adAffectGroup, adResyncUnderlyingValues
If intConflicts > 0 Then
strMsg = "A conflict occurred with updates for " & intConflicts & _
" record(s)." & vbCrLf & "The values will be restored" & _
" to their original values." & vbCrLf & vbCrLf
objRs1.MoveFirst
While Not objRs1.EOF
strMsg = strMsg & "SHIPPER = " & objRs1("CompanyName") & vbCrLf
strMsg = strMsg & "Value = " & objRs1("Phone").Value & vbCrLf
strMsg = strMsg & "UnderlyingValue = " & _
objRs1("Phone").UnderlyingValue & vbCrLf
strMsg = strMsg & "OriginalValue = " & _
objRs1("Phone").OriginalValue & vbCrLf
strMsg = strMsg & vbCrLf & "Original value has been restored."
MsgBox strMsg, vbOKOnly, _
"Conflict " & objRs1.AbsolutePosition & _
" of " & intConflicts
objRs1("Phone").Value = objRs1("Phone").OriginalValue
objRs1.MoveNext
Wend
objRs1.UpdateBatch adAffectGroup
Else
'Other error occurred. Minimal handling in this example.
strMsg = "Errors occurred during the update. " & _
objRs1.ActiveConnection.Errors(0).Number & " " & _
objRs1.ActiveConnection.Errors(0).Description
End If
On Error GoTo 0
End If
objRs1.MoveFirst
objRs1.Close
Set objRs1 = Nothing
'EndConflicts
現在の Record または特定の Field の Status プロパティを使って、発生した競合の種類を判断できます。
エラー処理の詳細については、エラー処理に関する記事を参照してください。