VBA-Sync-Code-Feedback

Aug 18 2020

Ich hatte gehofft, einige Informationen zu einem Klassenmodul zu erhalten, das ich entwerfe, um das Boilerplate der Verwendung asynchroner Abfragen in VBA zu abstrahieren. cQueryable unterstützt sowohl synchrone als auch asynchrone Abfragen. Sie können also beispielsweise ein Paket aufrufen, um temporäre Tabellen zu füllen. Dies würde synchron erfolgen, da dies abgeschlossen sein soll, bevor Sie Ihre ausgewählten Abfragen ausführen. Danach würden Sie ausgewählte Abfragen für jede der temporären Tabellen asynchron ausführen.

Dieser Code abstrahiert wirklich nur einen Großteil der Funktionen in der ADODB-Bibliothek. Ich habe versucht, meine Eigenschaften und Methoden ähnlich zu benennen, wie es die Objekte in dieser Bibliothek nach Möglichkeit verwenden. Meine connectionString-Eigenschaft hat einen ähnlichen Namen wie die im ADODB.Connection-Objekt. Meine CreateParam-Methode hat einen ähnlichen Namen wie die createParameter-Methode des ADODB.Command-Objekts.

Einige der neuen Verfahren, die ich eingeführt habe, sind die SQL-Eigenschaft. Dies enthält die auszuführende SQL-Abfrage (diese wird dem Befehlstext im Befehlsobjekt zugeordnet). Ein anderes ist ProcedureAfterQuery. Hiermit wird die Namensprozedur gespeichert, die vom Verbindungsobjekt aufgerufen werden soll, nachdem ein Ereignis ausgelöst wurde, wenn die Abfrage abgeschlossen ist. Andere sind SyncExecute und AsyncExecute, die beschreiben sollen, was sie in ihren Namen tun.

Bei diesen beiden ist zu beachten, dass SyncExecute eine Funktion ist, während AsyncExecute eine Unterroutine ist. Ich wollte, dass SyncExecute nach Abschluss ein Recordset zurückgibt. Aber für AsyncExecute wollte ich, dass es ein Sub ist, von dem ich nicht implizieren wollte, dass es etwas zurückgibt. Ich benutze dazu ähnlichen (aber unterschiedlichen) Code. Ich glaube, ich verstoße gegen das DRY-Prinzip. Ich könnte diese beiden konsolidieren, um eine gemeinsame Subroutinenprozedur aufzurufen. Diese gemeinsame Prozedur wäre dann komplizierter, aber der Code würde zumindest gemeinsam genutzt. Ich habe auf die eine oder andere Weise keine Präferenz.

Obwohl CreateParam der CreateParameter-Methode des Befehlsobjekts ähnelt, gibt es zwei Unterschiede. Zum einen ist die Reihenfolge der Argumente unterschiedlich. Dies liegt hauptsächlich daran, dass die Parameter für Größe und Richtung als optionale Parameter mit Standardwerten aufgeführt sind. Ihre Standardwerte können nur verwendet werden, wenn der Wert numerisch ist. Die Größe muss jedoch angegeben werden, wenn der Wert eine Zeichenfolge ist. In bestimmten Situationen ist die Größe optional, in anderen ist sie erforderlich. Und die Abfrage schlägt fehl, wenn sie nicht bereitgestellt wird.

Andere Dinge, die ich nicht berücksichtigt (oder getestet) habe, sind, dass ich gelesen habe, dass ADODB im Wesentlichen überall dort verwendet werden kann, wo ein Treiber bereitgestellt werden kann. Dies könnte also in Excel-Arbeitsmappen, möglicherweise Textdateien und anderen Quellen und nicht nur in Datenbanken verwendet werden. Vielleicht würden die synchronen und asynchronen Abfragen auch dort funktionieren. Aber das ist nicht das, was ich entwerfen oder testen wollte.

Ich würde mich über konstruktive Kritik freuen.

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

Antworten

2 TinMan Aug 18 2020 at 06:27

Private Funktion connectionSuccessful () Als Boolescher Wert

Der Name deutet darauf hin, dass Sie testen, ob die Verbindung bereits geöffnet wurde, obwohl sie tatsächlich zum Öffnen der Verbindung verwendet wird, und testen, ob sie erfolgreich war.

Private Function OpenConnection() As Boolean   

Dieser Name sagt Ihnen, dass Sie eine Verbindung öffnen. Da der Rückgabetyp Boolean ist, wird natürlich davon ausgegangen, dass die Funktion nur dann True zurückgibt, wenn die Verbindung erfolgreich war.

Es ist kontraproduktiv, wenn der Fehlerbehandler Fehler maskiert und eine Nachricht im Direktfenster druckt. Als Entwickler suche ich nicht instinktiv im Fehlerfenster nach Fehlermeldungen. Als Benutzer werde ich den Entwickler über die Fehlermeldung informieren, die später und nicht zum Zeitpunkt des Aufpralls angezeigt wurde. In Anbetracht der Tatsache, dass Ihr Code Rückrufverfahren verwendet, gibt es keine Garantie dafür, dass jemals ein Fehler ausgelöst wird. Das einzige, was sicher ist, ist, dass es irgendwann Probleme geben wird.

Sie sollten auf jeden Fall einen benutzerdefinierten Fehler auslösen, wenn dieser mConnectionStringnicht festgelegt ist. Eine benutzerdefinierte Fehlermeldung für die fehlgeschlagene Verbindung ist nicht erforderlich (wenn Sie die Fehlerbehandlungsroutine entfernen), da an der Stelle, an der diese Prozedur aufgerufen wurde, ein ADODB-Fehler ausgegeben wird.

Public Sub AsyncExecute ()

Ziehen Sie in Betracht, einen Fehler auszulösen, wenn die Rückrufprozedur nicht festgelegt ist.

Private Sub Class_Terminate ()

Diese Methode sollte verwendet werden, um die Verbindung zu schließen.

mConn, mASyncConn und mSyncConn

Es müssen keine drei verschiedenen Verbindungsvariablen verwendet werden. Sie erledigen mehr Arbeit und verschleiern den Code. Wenn Sie eine Variable wie verwenden AsyncMode As Boolean, erhalten Sie das gleiche Feedback und vereinfachen den Code, um das Lesen zu erleichtern.

Regeln der Namensgebung

Mit valueund in executeKleinbuchstaben wird die Groß- und Kleinschreibung für alle anderen Variablen und Eigenschaften mit demselben Namen geändert. Aus diesem Grund verwende ich Pascal Case für alle meine Variablen, die kein Präfix haben.

Mathieu Guindons Fabriken: Parametrisierte Objektinitialisierung

Andere mögliche Verbesserungen

Bei einem öffentlichen Ereignis können Sie es cQueryablein anderen benutzerdefinierten Klassen verwenden.

Public Event AsyncExecuteComplete(pRecordset As Recordset)

Die Fähigkeit, Abfragen miteinander zu verketten, scheint eine natürliche Übereinstimmung zu sein.

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

Auf diese Weise können Sie mehrere Abfragen der Reihe nach ausführen, ohne dass mehrere Rückrufe erforderlich sind.

CreateTempQuery.NextQuery(FillTempTableQuery).NextQuery(UpdateEmployeesTableQuery)