Commentaires sur le code VBA-Sync

Aug 18 2020

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

2 TinMan Aug 18 2020 at 06:27

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 mConnectionStringn'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 Booleanvous donnera le même retour d'information et simplifiera le code en le rendant plus facile à lire.

Conventions de nommage

Avoir valueet executeminuscules 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 cQueryabledans 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)