Comentarios sobre el código VBA-Sync
Esperaba obtener información sobre un módulo de clase que estoy diseñando para abstraer el texto estándar de la utilización de 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 tablas temporales. Esto se haría de forma sincrónica porque querría que esto se complete antes de ejecutar sus consultas seleccionadas. Después, ejecutaría consultas seleccionadas en cada una de las tablas temporales de forma asincrónica.
Este código realmente simplemente abstrae gran parte de la funcionalidad en la biblioteca ADODB. Intenté 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 mismo en el 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 mantiene la consulta SQL para ser ejecutada (esto se asigna al texto de comando en el objeto de comando). Otro es ProcedureAfterQuery. Esto es para mantener el procedimiento de nombre que será llamado por el objeto de conexión después de que genera un evento cuando se completa 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 dar a entender que devolvía nada. Utilizo un código similar (pero diferente) para hacer esto. Entonces creo que violé el principio DRY. Podría consolidar estos dos para llamar a un procedimiento de subrutina compartido. Ese procedimiento compartido sería entonces más complicado, pero el código al menos se compartiría. No tengo ninguna preferencia de una forma u otra.
Aunque CreateParam es similar al método CreateParameter del objeto de comando, existen 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 el tamaño debe especificarse 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, tal vez 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 las 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
Respuestas
Conexión de función privada Exitoso () como booleano
El nombre sugiere que está probando para ver si la conexión ya se ha abierto cuando en realidad se usa para abrir la conexión y probar si tuvo éxito.
Private Function OpenConnection() As Boolean
Este nombre le indica que está abriendo una conexión. Dado que el tipo de retorno es booleano, es natural suponer que la función devolverá True solo si la conexión fue exitosa.
Hacer que el controlador de errores escape 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 sobre el mensaje de error que apareció 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 lanzará un error 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 usarse para cerrar la conexión.
mConn, mASyncConn y mSyncConn
No es necesario utilizar tres variables de conexión diferentes. Está 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, utilizo 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 usarlo cQueryable
en otras clases personalizadas.
Public Event AsyncExecuteComplete(pRecordset As Recordset)
La capacidad de encadenar consultas juntas 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 la necesidad de varias devoluciones de llamada.
CreateTempQuery.NextQuery(FillTempTableQuery).NextQuery(UpdateEmployeesTableQuery)