Отзыв о коде VBA-Sync

Aug 18 2020

Я надеялся получить какие-то данные по модулю класса, который я разрабатываю, чтобы абстрагироваться от шаблонного использования асинхронных запросов в VBA. cQueryable поддерживает как синхронные, так и асинхронные запросы. Таким образом, вы можете сделать что-то вроде вызова пакета для заполнения временных таблиц. Это будет выполняться синхронно, потому что вы хотите, чтобы это было выполнено до того, как вы выполните выбранные запросы. После этого вы должны выполнить запросы на выборку для каждой из временных таблиц асинхронно.

Этот код действительно просто абстрагирует многие функции библиотеки ADODB. Я попытался назвать свои свойства и методы так же, как и те, которые используются объектами в этой библиотеке, где это возможно. Мое свойство connectionString названо так же, как и в объекте ADODB.Connection. И мой метод CreateParam назван так же, как метод createParameter объекта ADODB.Command.

Некоторые из новых процедур, которые я ввел, - это свойство sql. Он содержит SQL-запрос, который должен быть выполнен (это отображается на командный текст в командном объекте). Другой - ProcedureAfterQuery. Это должно удерживать процедуру имени, которая будет вызываться объектом соединения после того, как он вызовет событие, когда запрос завершится. Другие - SyncExecute и AsyncExecute, которые должны описывать в своих именах, что они делают.

Об этих двух вещах следует отметить то, что SyncExecute - это функция, а AsyncExecute - это подпрограмма. Я хотел, чтобы SyncExecute возвращал набор записей по завершении. Но для AsyncExecute я хотел, чтобы это была подпрограмма, я не хотел подразумевать, что она что-то возвращает. Для этого я использую похожий (но другой) код. Поэтому я считаю, что нарушаю принцип DRY. Я мог бы объединить эти два, чтобы вызвать одну общую процедуру подпрограммы. Тогда эта общая процедура была бы более сложной, но код, по крайней мере, был бы общим. Так или иначе, у меня нет предпочтений.

Хотя CreateParam похож на метод CreateParameter объекта команды, есть два отличия. Во-первых, порядок аргументов другой. Это в основном потому, что параметры размера и направления указаны как необязательные параметры со значениями по умолчанию. Их значения по умолчанию можно использовать только тогда, когда значение является числовым, но размер должен быть указан, если значение является строкой. Таким образом, в некоторых ситуациях размер необязателен, а в других - необходим. И запрос не будет выполнен, если он не предоставлен.

Другие вещи, которые я не рассматривал (или не тестировал), это то, что я читал, что ADODB можно использовать практически везде, где может быть предоставлен драйвер. Таким образом, это можно использовать в книгах Excel, возможно, текстовых файлах и других источниках, а не только в базах данных. Так что, возможно, там также будут работать синхронные и асинхронные запросы. Но это не то, что я намеревался разработать или протестировать.

Буду признателен за конструктивную критику.

VERSION 1.0 CLASS
BEGIN
  MultiUse = -1  'True
END
Attribute VB_Name = "cQueryable"
Attribute VB_GlobalNameSpace = False
Attribute VB_Creatable = False
Attribute VB_PredeclaredId = False
Attribute VB_Exposed = False
Option Explicit

'Requires a refernce to the Microsoft ActiveX Data Objects 6.1 Library (or equivalent)

Private WithEvents mASyncConn As ADODB.Connection
Attribute mASyncConn.VB_VarHelpID = -1
Private mSyncConn As ADODB.Connection
Private mConn As ADODB.Connection
Private mComm As ADODB.Command
Private mSql As String
Private mProcedureAfterQuery As String
Private mAsync As Boolean
Private mConnectionString As String

Private Const mSyncExecute As Long = -1

Private Sub Class_Initialize()
    Set mComm = New ADODB.Command
    Set mConn = New ADODB.Connection
End Sub

Public Property Let Sql(value As String)
    mSql = value
End Property

Public Property Get Sql() As String
    Sql = mSql
End Property

Public Property Let ConnectionString(value As String)
    mConnectionString = value
End Property

Public Property Get ConnectionString() As String
    ConnectionString = mConnectionString
End Property

Public Property Let procedureAfterQuery(value As String)
    mProcedureAfterQuery = value
End Property

Public Property Get procedureAfterQuery() As String
    procedureAfterQuery = mProcedureAfterQuery
End Property

Public Sub createParam(pName As String, pType As DataTypeEnum, pValue As Variant, Optional pDirection As ParameterDirectionEnum = adParamInput, Optional pSize As Long = 0)
    Dim pm As ADODB.Parameter
    With mComm
       Set pm = .CreateParameter(name:=pName, Type:=pType, direction:=pDirection, value:=pValue, size:=pSize)
       .Parameters.Append pm
    End With
End Sub

Public Function SyncExecute()
    Set mSyncConn = mConn
    If connectionSuccessful Then
        With mComm
            .CommandText = mSql
            Set .ActiveConnection = mSyncConn
            Set SyncExecute = .execute(Options:=mSyncExecute)
        End With
    End If
End Function

Public Sub AsyncExecute()
    Set mASyncConn = mConn
    If connectionSuccessful Then
        With mComm
            .CommandText = mSql
            Set .ActiveConnection = mASyncConn
            .execute Options:=adAsyncExecute
        End With
    End If
End Sub

Private Function connectionSuccessful() As Boolean
    If mConn.State = adStateClosed Then
        mConn.ConnectionString = mConnectionString
    End If
    
    On Error GoTo errHandler
        If mConn.State = adStateClosed Then
            mConn.Open
        End If
    
        connectionSuccessful = (mConn.State = adStateOpen)
    On Error GoTo 0
    
    Exit Function
errHandler:
    Debug.Print "Error: Connection unsuccessful"
    connectionSuccessful = False
End Function

Private Sub mASyncConn_ExecuteComplete(ByVal RecordsAffected As Long, ByVal pError As ADODB.Error, adStatus As ADODB.EventStatusEnum, ByVal pCommand As ADODB.Command, ByVal pRecordset As ADODB.Recordset, ByVal pConnection As ADODB.Connection)
    If mProcedureAfterQuery <> "" Then
        Call Application.Run(mProcedureAfterQuery, pRecordset)
    End If
End Sub

Ответы

2 TinMan Aug 18 2020 at 06:27

Подключение частной функции Успешно () как логическое

Название предполагает, что вы тестируете, было ли соединение уже открыто, хотя на самом деле оно используется для открытия соединения и проверки, было ли оно успешным.

Private Function OpenConnection() As Boolean   

Это имя говорит вам, что вы открываете соединение. Поскольку тип возвращаемого значения является логическим, естественно предположить, что функция вернет True только в том случае, если соединение было успешным.

Если обработчик ошибок избегает ошибок и выводит сообщение в окно немедленного выполнения, это контрпродуктивно. Как разработчик, я не смотрю инстинктивно в окно Immediate Window на предмет сообщений об ошибках. Как пользователь я буду уведомлять разработчика о сообщении об ошибке, которое появилось в конце строки, а не в точке воздействия. Учитывая, что ваш код использует процедуры обратного вызова, нет никакой гарантии, что когда-либо возникнет ошибка. Единственное, что можно сказать наверняка, - это то, что где-то в будущем возникнут проблемы.

Вы обязательно должны вызвать пользовательскую ошибку, если она mConnectionStringне установлена. Пользовательское сообщение об ошибке для неудачного подключения не требуется (если вы удалите обработчик ошибок), потому что ошибка ADODB будет выдана в точке, где была вызвана эта процедура.

Публичная подпрограмма AsyncExecute ()

Рассмотрите возможность возникновения ошибки, если процедура обратного вызова не установлена.

Частный подкласс_Terminate ()

Этот метод следует использовать для закрытия соединения.

mConn, mASyncConn и mSyncConn

Нет необходимости использовать три разные переменные подключения. Вы делаете больше работы и запутываете код. Использование переменной, такой как AsyncMode As Boolean, даст вам такую ​​же обратную связь и упростит код, облегчая его чтение.

Соглашения об именах

Наличие valueи executeнижний регистр изменяет регистр для всех других переменных и свойств с такими же именами. По этой причине я использую Pascal Case для всех своих переменных, у которых нет какого-либо префикса.

Фабрики Матье Гиндона : Инициализация параметризованного объекта

Другие возможные улучшения

Открытое событие позволит вам использовать его cQueryableв других пользовательских классах.

Public Event AsyncExecuteComplete(pRecordset As Recordset)

Возможность объединять запросы в цепочку кажется естественной.

Public Function NextQuery(Queryable AS cQueryable) AS cQueryable
   Set NextQuery = Queryable 
   Set mQueryable = Queryable 
End Function

Это позволит вам выполнять несколько запросов по порядку без необходимости многократного обратного вызова.

CreateTempQuery.NextQuery(FillTempTableQuery).NextQuery(UpdateEmployeesTableQuery)