Feedback sul codice VBA-Sync
Speravo di ottenere qualche input su un modulo di classe che sto progettando per astrarre il boilerplate dell'utilizzo di query asincrone in VBA. cQueryable supporta query sia sincrone che asincrone. Quindi potresti fare qualcosa come chiamare un pacchetto per popolare le tabelle temporanee. Questo sarebbe fatto in modo sincrono perché vorresti che fosse completato prima di eseguire le query di selezione. Successivamente, eseguirai le query di selezione su ciascuna delle tabelle temporanee in modo asincrono.
Questo codice in realtà astrae solo molte delle funzionalità nella libreria ADODB. Ho provato a denominare le mie proprietà e metodi in modo simile a quello che usano gli oggetti in quella libreria, ove possibile. La mia proprietà connectionString è denominata in modo simile alla stessa nell'oggetto ADODB.Connection. E il mio metodo CreateParam è denominato in modo simile al metodo createParameter dell'oggetto ADODB.Command.
Alcune delle nuove procedure che ho introdotto sono la proprietà sql. Contiene la query sql da eseguire (questa mappa viene associata al testo di comando nell'oggetto comando). Un altro è ProcedureAfterQuery. Questo serve per mantenere la procedura del nome che deve essere chiamata dall'oggetto connessione dopo che genera un evento al termine della query. Altri sono SyncExecute e AsyncExecute che dovrebbero descrivere ciò che fanno nei loro nomi.
Una cosa da notare su questi due è che SyncExecute è una funzione mentre AsyncExecute è una subroutine. Volevo che SyncExecute restituisse un recordset al termine. Ma per AsyncExecute, volevo che fosse un sottotitolo, non volevo implicare che restituisse qualcosa. Uso un codice simile (ma diverso) per farlo. Quindi penso di violare il principio DRY. Potrei consolidare questi due per chiamare una procedura di subroutine condivisa. Quella procedura condivisa sarebbe quindi più complicata, ma il codice sarebbe almeno condiviso. Non ho preferenze in un modo o nell'altro.
Sebbene CreateParam sia simile al metodo CreateParameter dell'oggetto comando, esistono due differenze. Uno è che l'ordine degli argomenti è diverso. Ciò è dovuto principalmente al fatto che i parametri di dimensione e direzione sono elencati come parametri opzionali con valori predefiniti. I loro valori predefiniti possono essere utilizzati solo quando il valore è numerico, ma è necessario specificare la dimensione se il valore è una stringa. Quindi in alcune situazioni, la dimensione è facoltativa, mentre in altre è richiesta. E la query fallirà se non viene fornita.
Altre cose che non ho considerato (o testato) è che ho letto che ADODB può essere utilizzato essenzialmente ovunque possa essere fornito un driver. Quindi questo potrebbe essere utilizzato su cartelle di lavoro di Excel, forse file di testo e altre fonti piuttosto che solo database. Quindi forse le query sincrone e asincrone funzionerebbero anche lì. Ma non è quello che ho deciso di progettare o testare.
Apprezzerei le critiche costruttive.
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
Risposte
Funzione privata connectionSuccessful () As Boolean
Il nome suggerisce che stai testando per vedere se la connessione è già stata aperta quando in realtà viene utilizzata per aprire la connessione e testare se ha avuto successo.
Private Function OpenConnection() As Boolean
Questo nome ti dice che stai aprendo una connessione. Poiché il tipo restituito è booleano, è naturale presumere che la funzione restituirà True solo se la connessione ha avuto successo.
Avere il gestore degli errori di sfuggire agli errori e stampare un messaggio nella finestra immediata è controproducente. In qualità di sviluppatore non guardo istintivamente alla finestra immediata per i messaggi di errore. Come utente notificherò allo sviluppatore il messaggio di errore che è stato generato lungo la linea e non nel punto di impatto. Considerando che il codice utilizza procedure di callback, non vi è alcuna garanzia che verrà mai generato un errore. L'unica cosa certa è che ci saranno problemi da qualche parte lungo la linea.
Dovresti sicuramente generare un errore personalizzato se mConnectionString
non è impostato. Un messaggio di errore personalizzato per la connessione non riuscita non è necessario (se si rimuove il gestore degli errori) perché verrà generato un errore ADODB nel punto in cui è stata chiamata questa procedura.
Public Sub AsyncExecute ()
Considerare la generazione di un errore se la procedura di callback non è impostata.
Sottoclasse privata_Terminate ()
Questo metodo dovrebbe essere utilizzato per chiudere la connessione.
mConn, mASyncConn e mSyncConn
Non è necessario utilizzare tre diverse variabili di connessione. Stai facendo più lavoro e offuscando il codice. L'uso di una variabile come AsyncMode As Boolean
ti darà lo stesso feedback e semplificherà il codice rendendolo più facile da leggere.
Convenzioni di denominazione
Avere value
e execute
minuscole cambia il caso di tutte le altre variabili e le proprietà con gli stessi nomi. Per questo motivo, utilizzo Pascal Case per tutte le mie variabili che non hanno una sorta di prefisso.
Fabbriche di Mathieu Guindon : inizializzazione di oggetti con parametri
Altri possibili miglioramenti
Un evento pubblico ti consentirebbe di utilizzare cQueryable
in altre classi personalizzate.
Public Event AsyncExecuteComplete(pRecordset As Recordset)
La capacità di concatenare le query insieme sembra una scelta naturale.
Public Function NextQuery(Queryable AS cQueryable) AS cQueryable Set NextQuery = Queryable Set mQueryable = Queryable End Function
Ciò ti consentirà di eseguire più query in ordine senza la necessità di più callback.
CreateTempQuery.NextQuery(FillTempTableQuery).NextQuery(UpdateEmployeesTableQuery)