Commentaires sur le code VBA-Sync
J'espérais obtenir des informations sur un module de classe que je conçois pour résumer le passe-partout de l'utilisation des requêtes asynchrones dans VBA. cQueryable prend en charge les requêtes synchrones et asynchrones. Vous pouvez donc faire quelque chose comme appeler un package pour remplir les tables temporaires. Cela serait fait de manière synchrone, car vous voudriez que cela soit terminé avant d'exécuter vos requêtes de sélection. Ensuite, vous exécuteriez des requêtes de sélection sur chacune des tables temporaires de manière asynchrone.
Ce code ne fait que résumer une grande partie des fonctionnalités de la bibliothèque ADODB. J'ai essayé de nommer mes propriétés et méthodes de la même manière que les objets de cette bibliothèque utilisent lorsque cela est possible. Ma propriété connectionString est nommée de la même manière que celle de l'objet ADODB.Connection. Et ma méthode CreateParam est nommée de la même manière que la méthode createParameter de l'objet ADODB.Command.
Quelques-unes des nouvelles procédures que j'ai introduites sont la propriété sql. Cela contient la requête SQL à exécuter (cela correspond au texte de commande dans l'objet de commande). Un autre est ProcedureAfterQuery. Cela contient la procédure de nom à appeler par l'objet de connexion après avoir déclenché un événement à la fin de la requête. D'autres sont SyncExecute et AsyncExecute qui devraient décrire ce qu'ils font dans leurs noms.
Une chose à noter à propos de ces deux est que SyncExecute est une fonction alors qu'AsyncExecute est un sous-programme. Je voulais que SyncExecute renvoie un jeu d'enregistrements une fois terminé. Mais pour AsyncExecute, je voulais que ce soit un sous-marin que je ne voulais pas laisser entendre qu'il retournait quoi que ce soit. J'utilise un code similaire (mais différent) pour ce faire. Je pense donc que je viole le principe DRY. Je pourrais consolider ces deux pour appeler une procédure de sous-programme partagée. Cette procédure partagée serait alors plus compliquée, mais le code serait au moins partagé. Je n'ai pas de préférence d'une manière ou d'une autre.
Bien que CreateParam soit similaire à la méthode CreateParameter de l'objet de commande, il existe deux différences. La première est que l'ordre des arguments est différent. Ceci est principalement dû au fait que les paramètres de taille et de direction sont répertoriés comme paramètres facultatifs avec des valeurs par défaut. Leurs valeurs par défaut ne peuvent être utilisées que lorsque la valeur est numérique, mais la taille doit être spécifiée si la valeur est une chaîne. Ainsi, dans certaines situations, la taille est facultative, alors que dans d'autres, elle est obligatoire. Et la requête échouera si elle n'est pas fournie.
Une autre chose que je n'ai pas considérée (ou testée) est que j'ai lu que l'ADODB peut être utilisé essentiellement partout où un pilote peut être fourni. Cela pourrait donc être utilisé sur des classeurs Excel, peut-être des fichiers texte et d'autres sources plutôt que de simples bases de données. Alors peut-être que les requêtes synchrones et asynchrones fonctionneraient là aussi. Mais ce n'est pas ce que j'ai voulu concevoir ou tester.
J'apprécierais les critiques constructives.
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
Réponses
Connexion de fonction privée Succès () As Boolean
Le nom suggère que vous testez pour voir si la connexion a déjà été ouverte alors qu'en fait elle est utilisée pour ouvrir la connexion et tester si elle a réussi.
Private Function OpenConnection() As Boolean
Ce nom vous indique que vous ouvrez une connexion. Étant donné que le type de retour est Boolean, il est naturel de supposer que la fonction retournera True uniquement si la connexion a réussi.
Le fait que le gestionnaire d'erreurs évite les erreurs et imprime un message dans la fenêtre d'exécution est contre-productif. En tant que développeur, je ne regarde pas instinctivement la fenêtre d'exécution pour les messages d'erreur. En tant qu'utilisateur, j'informerai le développeur du message d'erreur qui a été soulevé sur la ligne et non au point d'impact. Étant donné que votre code utilise des procédures de rappel, il n'y a aucune garantie qu'une erreur sera jamais déclenchée. La seule chose qui soit certaine, c'est qu'il y aura des problèmes quelque part sur toute la ligne.
Vous devez absolument signaler une erreur personnalisée si elle mConnectionString
n'est pas définie. Un message d'erreur personnalisé pour l'échec de la connexion n'est pas nécessaire (si vous supprimez le gestionnaire d'erreurs) car une erreur ADODB sera levée au point où cette procédure a été appelée.
Public Sub AsyncExecute ()
Envisagez de générer une erreur si la procédure de rappel n'est pas définie.
Sous-classe privée Class_Terminate ()
Cette méthode doit être utilisée pour fermer la connexion.
mConn, mASyncConn et mSyncConn
Il n'est pas nécessaire d'utiliser trois variables de connexion différentes. Vous travaillez plus et obscurcissez le code. L'utilisation d'une variable telle que AsyncMode As Boolean
vous donnera le même retour d'information et simplifiera le code en le rendant plus facile à lire.
Conventions de nommage
Avoir value
et execute
minuscules change la casse pour toutes les autres variables et propriétés avec les mêmes noms. Pour cette raison, j'utilise Pascal Case pour toutes mes variables qui n'ont pas de préfixe.
Usines de Mathieu Guindon : initialisation d'objets paramétrés
Autres améliorations possibles
Un événement public vous permettrait de l'utiliser cQueryable
dans d'autres classes personnalisées.
Public Event AsyncExecuteComplete(pRecordset As Recordset)
La capacité à enchaîner les requêtes semble être une solution naturelle.
Public Function NextQuery(Queryable AS cQueryable) AS cQueryable Set NextQuery = Queryable Set mQueryable = Queryable End Function
Cela vous permettra d'exécuter plusieurs requêtes dans l'ordre sans avoir besoin de plusieurs rappels.
CreateTempQuery.NextQuery(FillTempTableQuery).NextQuery(UpdateEmployeesTableQuery)