Как использовать DataMacros для запуска VBA для регистрации изменений записи - PullRequest
2 голосов
/ 04 марта 2020

У меня есть база данных MS Access с несколькими таблицами, и у каждой таблицы есть разные поля (у каждой таблицы есть первичный ключ). Всякий раз, когда кто-то изменяет существующую запись, я хочу, чтобы статус «до / после» регистрировался в «хронологической» таблице, но я стараюсь избегать создания DataMacro для 20+ полей на таблицу, поскольку это кажется громоздким и сложным в управлении.

Для этого sh я добавил макросы «До изменения» и «После обновления», каждый из которых имеет один вызов SetLocalVar для запуска функций publi c vba, которые постоянно находятся в локальном модуле. Функция «before» циклически перебирает все поля, существующие в текущей таблице, для создания словаря значений перед изменением. Функция «после» повторяет процесс для идентификации измененного поля, так что информация может быть добавлена ​​в таблицу истории (содержит имя таблицы, имя поля, значения до / после, пользователь, отметку времени и т. Д. c.).

Проблема, с которой я столкнулся, заключается в том, что функция «После обновления» показывает только данные перед изменением. Я не могу сказать, что это новое значение или какое поле изменилось. Я не могу подать [Old]. [FieldName], [FieldName] в качестве входных данных для SetLocalVar, потому что я не знаю заранее, какие поля будут обновлены (и имена полей различаются в разных таблицах). И я не могу вызвать Requery или Refre sh, потому что это вступает в противоречие с процессом «После обновления».

Из всех подобных вопросов, заданных здесь и в других местах, которые я могу найти, они либо начинаются с явного знания того, какие поле изменится, или же не предоставит этот уровень детализации в своих событиях журнала. Или, может быть, мой гугл-фу слаб.

Вот что у меня есть:

До SetLocalVar выражение (другое имя входной таблицы для таблицы):

=SetupLogEvent("Table01",[KeyName])

После SetLocalVar выражение:

=SubmitLogEvent("Table01",[KeyName])

Код модуля

Public BeforeFields As Scripting.Dictionary

Public Function SetupLogEvent(ByVal TableName As String, ByVal KeyName As String)
    Set BeforeFields = New Scripting.Dictionary
    Set rs = CurrentDb.OpenRecordset("SELECT * FROM [" & TableName & "] WHERE KeyName='" & KeyName & "'")
    Dim i As Long
    With rs
        .MoveLast
        .MoveFirst
        For i = 0 To rs.Fields.Count - 1
            fName = rs.Fields(i).Name
            fVal = rs.Fields(i).Value
            BeforeFields.Add fName, fVal
        Next i
    End With
    rs.Close
    SetupLogEvent = True
End Function

Public Function SubmitLogEvent(ByVal TableName As String, ByVal KeyName As String)
    DoEvents
    Dim MakeUpdate As Boolean
    MakeUpdate = False
    Set rs = CurrentDb.OpenRecordset("SELECT * FROM [" & TableName & "] WHERE KeyName='" & KeyName & "'")
    With rs
        .MoveLast
        .MoveFirst
        For i = 0 To rs.Fields.Count - 1
            fName = rs.Fields(i).Name
            fVal = rs.Fields(i).Value
            If fVal <> BeforeFields(fName) Then
                Debug.Print ("Modified field is " & fName)
                MakeUpdate = True
                Exit For
            End If
        Next i
    End With
    rs.Close
    If MakeUpdate = True Then
        'Run SQL code to update the history table with the relevant information
        'DoCmd.RunSQL <SQL code here>
    End If
    Set BeforeFields = Nothing
    SubmitLogEvent = True
End Function

Я подозреваю, что должен использовать какой-то другой метод, помимо циклического перебора набора записей, но Я не уверен, что я должен делать вместо этого. Я полагаю, что я мог бы добавить пару десятков необязательных входных данных к функции after и вручную ввести каждое [FieldName] каждой таблицы в выражение SetLocalVar каждой таблицы, но я не могу представить себе систему настолько негибкой, чтобы требовать этого. Должен быть лучший способ, верно?

Редактировать: я обновил название этого вопроса и мою причину избегать написания DataMacro для каждого поля для будущих читателей этой топи c.

1 Ответ

0 голосов
/ 05 марта 2020

Спасибо braX, kri sh KM и SunKnight за ваши комментарии выше.

Я разработал процесс, который соответствует моим собственным потребностям, но я кратко объясню свои рассуждения:

Ограничения других методов:

  1. Решение Allenbrowne (1) требует, чтобы пользователь использовал форму, а (2) - ключи быть AutoNumbers (мое должно быть строковым текстом)
  2. Решение Scottgem исключает использование форм. Я предлагаю своим пользователям форму для быстрого обновления полей, с которыми они обычно взаимодействуют, но в некоторых таблицах есть более 20 полей, и любая форма, которую я им дам (12+ таблиц с более чем 20 уникальными полями), будет сложнее, чем просто они взаимодействуют с самими таблицами (хотя, как правило, это осуждается, в этом случае не возникает проблем с отображением таблиц, пока у нас есть некоторое отслеживание).
  3. Сгенерированные вручную DataMacros негибкие - я не могу скопируйте и вставьте любую их часть в новую таблицу, чтобы быстро обновить поля, которые я заинтересован в отслеживании (добавить, удалить, изменить и т. д. c.). Эта база данных является новой, поэтому я ожидаю, что приоритеты и фокус изменятся по мере добавления новых полей с течением времени.

Решение:

В конце моей публикации вопрос, я упомянул, что я мог бы передать обновленные значения в функцию с дополнительными входами, и сделать так, чтобы функция выполняла внутреннее сравнение до / после, так что я так и сделал.

Ограничения:

  1. DataMacros выдает ошибки, если вы не обрабатываете нулевые значения в выражении

  2. DataMacros имеют ограничение в 255 символов

Из-за # 1 выше, мне пришлось обернуть входные данные в функции Nz (). Моя схема также включает подачу входных данных в функцию «after» в формате ключ / значение, для чего требуется подробное объявление. С ограничением в 255 символов я решил отслеживать только 6-10 полей на таблицу за раз (чтобы сделать больше, я просто повторяю процесс во втором DataMacro).

Public TableChangedName As String
Public TableChangedKey As String
Public SuppressLog As Boolean

Public Function SetupLogEvent(ByVal TableName As String, ByVal KeyName As String)
    'This function reads the recordset as-is to build a 'before' dictionary

    'SuppressLog is True when updating via user form
    'The form will make its own submissions to the history log to specify which form did the update
    If SuppressLog = False Then
        Set BeforeFields = New Scripting.Dictionary
        Set rs = CurrentDb.OpenRecordset("SELECT * FROM [" & TableName & "] WHERE KeyName='" & KeyName & "'")
        Dim i As Long
        Do While Not rs.EOF
            rs.MoveLast
            rs.MoveFirst
            For i = 0 To rs.Fields.Count - 1
                fName = rs.Fields(i).Name
                fVal = rs.Fields(i).Value
                BeforeFields.Add fName, CStr(Nz(fVal, ""))
            Next i
            rs.MoveNext
        Loop
        rs.Close
        TableChangedName = TableName
        TableChangedKey = KeyName
        SetupLogEvent = True
    End If
End Function

Public Function SubmitLogEvent(Optional ByVal Input1 As String = "None", Optional ByVal Input2 As String = "None", Optional ByVal Input3 As String = "None", Optional ByVal Input4 As String = "None", Optional ByVal Input5 As String = "None", Optional ByVal Input6 As String = "None", Optional ByVal Input7 As String = "None", Optional ByVal Input8 As String = "None", Optional ByVal Input9 As String = "None", Optional ByVal Input10 As String = "None", Optional ByVal Input11 As String = "None", Optional ByVal Input12 As String = "None")
    'This function submits an entry to the _History table for each field that was changed, marking the change as "Manually updated" (i.e. no form used)

    'SuppressLog is True when updating via user form
    'The form will make its own submissions to the history log to specify which form did the update
    If SuppressLog = False Then
        Dim InputArray As Variant
        InputArray = Array(Input1, Input2, Input3, Input4, Input5, Input6, Input7, Input8, Input9, Input10, Input11, Input12)
        Set AfterFields = New Scripting.Dictionary
        For i = LBound(InputArray) To UBound(InputArray) Step 2
            If InputArray(i) = "None" Then
                'End of used input fields
                Exit For
            Else
                AfterFields.Add InputArray(i), InputArray(i + 1)
            End If
        Next i
        DoCmd.SetWarnings False
        For Each fName In AfterFields.Keys
            If BeforeFields(fName) <> AfterFields(fName) Then
                strSQL = "INSERT INTO _History ([TableModified],[KeyName],[Field],[From],[To],[User],[TimeStamp],[Method]) VALUES ('" & TableChangedName & "','" & TableChangedKey & "','" & fName & "','" & BeforeFields(fName) & "','" & AfterFields(fName) & "','" & Environ("username") & "','" & Now() & "','Manually updated')"
                Debug.Print ("strSQL = """ & strSQL & """")
                DoCmd.RunSQL strSQL
            End If
        Next fName
        DoCmd.SetWarnings True
    End If
    SubmitLogEvent = True
End Function

С этими двумя функции выше, единственное, что мне нужно обновить для каждой таблицы - это выражения.

До SetLocalVar выражение:

= SetupLogEvent ("Table01", [KeyName])

После SetLocalVar выражение:

= SubmitLogEvent ('Field1', Nz ([Field1]), 'Field2', Nz ([Field2]), 'Field3', Nz ([Field3]), 'Field4', Nz ([Field4]))


Если новое поле добавляется в одну или несколько таблиц, которые необходимо отслеживать, я обновляю переменную FieldList и перезапустите следующую функцию:

Private Function PrintExpression()
    'Enter the list of fields you want to track, separated by commas
    FieldList = "Field_1,Field_2,Field_3,Field_4,Field_5"

    exprString = "=SubmitLogEvent("
    For Each Entry In Split(FieldList, ",")
        exprString = exprString & "'" & Entry & "',Nz([" & Entry & "]),"
    Next
    exprString = RxReplace(exprString, ",$", ")")
    If Len(exprString) > 255 Then
        Call MsgBox("This result is > 255 characters (" & Len(exprString) & ") and will be rejected.", vbExclamation + vbOKOnly, "Input Too Long")
    Else
        Debug.Print ("=SetupLogEvent(""<<< Table Name >>>"",[KeyName])")
        Debug.Print (exprString)
    End If
End Function

Вывод:

= SetupLogEvent ("<<< Имя таблицы >>>", [KeyName])

= SubmitLogEvent ( 'Field_1', Nz ([Field_1]), 'Field_2', Nz ([Field_2]), 'Field_3', Nz ([Field_3]), 'Field_4', Nz ([Field_4]), 'Field_5 ', Nz ([Field_5]))

Я копирую / вставляю 2-е выражение в любой из таблиц, которые используют эти поля, и я готов. Если мне нужно отслеживать более 6-10 полей в таблице, я перезапускаю вышеупомянутую функцию со вторым FieldList и добавляю ее во второй DataMacro для таблицы. Промойте и повторите при необходимости.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...