code review sql – Comentarios sobre el código VBA-Sync

Pregunta:

Tenía la esperanza de obtener alguna información sobre un módulo de clase que estoy diseñando para abstraer el modelo de utilizar consultas asincrónicas en VBA. cQueryable admite consultas sincrónicas y asincrónicas. Entonces, podría hacer algo como llamar a un paquete para completar las tablas temporales. Esto se haría de forma síncrona porque desearía que se completara antes de ejecutar las consultas de selección. Después, ejecutaría consultas de selección en cada una de las tablas temporales de forma asincrónica.

Este código realmente abstrae gran parte de la funcionalidad de la biblioteca ADODB. Traté de nombrar mis propiedades y métodos de manera similar a lo que usan los objetos en esa biblioteca cuando sea posible. Mi propiedad connectionString tiene un nombre similar al del objeto ADODB.Connection. Y mi método CreateParam tiene un nombre similar al método createParameter del objeto ADODB.Command.

Algunos de los nuevos procedimientos que he introducido son la propiedad sql. Esto contiene la consulta sql que se ejecutará (esto se asigna al texto de comando en el objeto de comando). Otro es ProcedimientoDespuésDeQuery. Esto es para mantener el procedimiento de nombre que llamará el objeto de conexión después de que genere un evento cuando se complete la consulta. Otros son SyncExecute y AsyncExecute, que deberían describir lo que hacen en sus nombres.

Una cosa a tener en cuenta sobre estos dos es que SyncExecute es una función, mientras que AsyncExecute es una subrutina. Quería que SyncExecute devolviera un conjunto de registros cuando se complete. Pero para AsyncExecute, quería que fuera un sub. No quería implicar que devolviera nada. Uso un código similar (pero diferente) para hacer esto. Entonces creo que violo el principio SECO. Podría consolidar estos dos para llamar a un procedimiento de subrutina compartida. Ese procedimiento compartido sería entonces más complicado, pero al menos el código sería compartido. No tengo preferencia de un modo u otro.

Aunque CreateParam es similar al método CreateParameter del objeto de comando, hay dos diferencias. Una es que el orden de los argumentos es diferente. Esto se debe principalmente a que los parámetros de tamaño y dirección se enumeran como parámetros opcionales con valores predeterminados. Sus valores predeterminados solo se pueden usar cuando el valor es numérico, pero se debe especificar el tamaño si el valor es una cadena. Entonces, en ciertas situaciones, el tamaño es opcional, mientras que en otras es obligatorio. Y la consulta fallará si no se proporciona.

Otras cosas que no consideré (o probé) es que he leído que ADODB se puede usar esencialmente en cualquier lugar donde se pueda proporcionar un controlador. Por lo tanto, esto podría usarse en libros de Excel, quizás archivos de texto y otras fuentes en lugar de solo bases de datos. Entonces, quizás las consultas sincrónicas y asincrónicas también funcionen allí. Pero eso no es lo que me propuse diseñar o probar.

Agradecería críticas constructivas.

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

Respuesta:

Conexión de función privada exitosa () como booleano

El nombre sugiere que está probando para ver si la conexión ya se abrió cuando, de hecho, se usa para abrir la conexión y probar si fue exitosa.

Private Function OpenConnection() As Boolean   

Este nombre le indica que está abriendo una conexión. Dado que el tipo de devolución es booleano, es natural suponer que la función devolverá True solo si la conexión se realizó correctamente.

Hacer que el controlador de errores evite los errores e imprima un mensaje en la ventana Inmediato es contraproducente. Como desarrollador, no busco instintivamente mensajes de error en la ventana Inmediato. Como usuario, notificaré al desarrollador el mensaje de error que se generó en la línea y no en el punto de impacto. Teniendo en cuenta que su código utiliza procedimientos de devolución de llamada, no hay garantía de que alguna vez se genere un error. Lo único que es seguro es que habrá problemas en algún momento.

Definitivamente debería generar un error personalizado si mConnectionString no está configurado. No es necesario un mensaje de error personalizado para la conexión fallida (si elimina el controlador de errores) porque se generará un error de ADODB en el punto donde se llamó a este procedimiento.

Public Sub AsyncExecute ()

Considere generar un error si el procedimiento de devolución de llamada no está configurado.

Subclase privada_Terminate()

Este método debe utilizarse para cerrar la conexión.

mConn, mASyncConn y mSyncConn

No es necesario utilizar tres variables de conexión diferentes. Estás haciendo más trabajo y ofuscando el código. El uso de una variable como AsyncMode As Boolean le dará la misma información y simplificará el código para que sea más fácil de leer.

Convenciones de nombres

Tener value y execute minúsculas cambia el caso de todas las demás variables y propiedades con los mismos nombres. Por esta razón, uso Pascal Case para todas mis variables que no tienen algún tipo de prefijo.

Fábricas de Mathieu Guindon : inicialización de objetos parametrizados

Otras posibles mejoras

Un evento público le permitiría usar cQueryable en otras clases personalizadas.

Public Event AsyncExecuteComplete(pRecordset As Recordset)

La capacidad de encadenar consultas parece un ajuste natural.

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

Esto le permitirá ejecutar varias consultas en orden sin necesidad de múltiples devoluciones de llamada.

CreateTempQuery.NextQuery(FillTempTableQuery).NextQuery(UpdateEmployeesTableQuery)

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top

web tasarım