Обнаружение и разрешение конфликтов

Область применения: Access 2013, Office 2013

Detecting and Resolving Conflicts

Если вы работаете с набором записей в немедленном режиме, вероятность возникновения проблем с параллелизмом значительно меньше. С другой стороны, если приложение использует пакетное обновление, может быть велика вероятность того, что один пользователь изменит запись, прежде чем будут сохранены изменения, внесенные другим пользователем, изменяющим ту же запись. В этом случае приложение должно корректно обрабатывать конфликт. Возможно, вы хотите, чтобы последний пользователь, отправляющий обновление на сервер, "выиграл". Или вы можете позволить последнему пользователю решить, какое обновление должно иметь приоритет, предоставив ему выбор между двумя конфликтующими значениями.

В любом случае ADO предоставляет свойства UnderlyingValue и OriginalValue объекта Field для обработки таких типов конфликтов. Используйте эти свойства в сочетании с методом Resync и свойством Filterнабора записей.

Detecting Errors

При возникновении конфликта ADO во время пакетного обновления в коллекцию Ошибок будет помещено предупреждение. Поэтому всегда следует проверка об ошибках сразу после вызова BatchUpdate, а если вы обнаружите их, приступить к проверке предположения о том, что у вас возник конфликт. Первым шагом является установка свойства Filter в наборе записей , равного adFilterConflictingRecords (свойство Filter рассматривается в предыдущей главе). Это ограничивает представление в наборе записей только теми записями, которые находятся в конфликте. Если после этого шага свойство RecordCount равно нулю, известно, что ошибка была вызвана не конфликтом.

При вызове BatchUpdate ADO и поставщик создают инструкции SQL для обновления источника данных. Помните, что некоторые источники данных имеют ограничения на то, какие типы столбцов можно использовать в предложении WHERE.

Затем вызовите метод Resync в наборе Recordset с аргументом AffectRecords , равным adAffectGroup , а аргумент ResyncValues — adResyncUnderlyingValues. Метод Resync обновляет данные в текущем объекте Recordset из базовой базы данных. С помощью adAffectGroup вы гарантируете, что только записи, видимые с текущим параметром фильтра, то есть только конфликтующие записи, будут повторно синхронизироваться с базой данных. Это может существенно повлиять на производительность, если вы работаете с большим набором записей. Задав для аргумента ResyncValues значение adResyncUnderlyingValues при вызове Resync, вы убедитесь, что свойство UnderlyingValue будет содержать (конфликтующее) значение из базы данных, свойство Value будет поддерживать значение, введенное пользователем, и что свойство OriginalValue будет содержать исходное значение поля (значение, которое оно имело до последнего успешного вызова UpdateBatch ). Затем эти значения можно использовать для решения конфликта программным путем или требовать от пользователя выбора значения, которое будет использоваться.

Этот способ показан в примере кода ниже. В этом примере искусственно создается конфликт, используя отдельный набор записей для изменения значения в базовой таблице перед вызовом UpdateBatch .

 
'BeginConflicts 
    On Error GoTo ErrHandler: 
     
    Dim objRs1 As New ADODB.Recordset 
    Dim objRs2 As New ADODB.Recordset 
    Dim strSQL As String 
    Dim strMsg As String 
     
    strSQL = "SELECT * FROM Shippers WHERE ShipperID = 2" 
                  
    'Open Rs and change a value 
    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 
     
    'Clean up 
    objRs1.Close 
    Set objRs1 = Nothing 
    Exit Sub 
     
ErrHandler: 
    
    If Not objRs1 Is Nothing Then 
        If objRs1.State = adStateOpen Then objRs1.Close 
        Set objRs1 = Nothing 
    End If 
     
    If Not objRs2 Is Nothing Then 
        If objRs2.State = adStateOpen Then objRs2.Close 
        Set objRs2 = Nothing 
    End If 
     
    If Err <> 0 Then 
        MsgBox Err.Source & "-->" & Err.Description, , "Error" 
    End If 
     
'EndConflicts 

Чтобы определить тип конфликта, можно использовать свойство Status текущей записи или определенного поля .

Более подробные сведения об обработке ошибок см. в разделе Глава 6. Обработка ошибок.