So vereinfachen Sie den Aufruf von Stored Procedures in ADO

Veröffentlicht: 15. Dez 2001 | Aktualisiert: 19. Jun 2004

Von Ken Spencer

Über ein leicht modifiziertes Add-In als Assistent, das aus einer Microsoft-Beispielanwendung stammt, können Stored Procedures wesentlich schneller in Visual Basic-Code integriert werden.

Auf dieser Seite

Mit einem eigenen Add-in
Änderung des Programmbeispiels
In den Tiefen des Codes
Noch mehr Code
Die Arbeit mit dem neuen Zusatzmodul
Fazit

Diesen Artikel können Sie hier lesen dank freundlicher Unterstützung der Zeitschrift:

Bild03

Mit einem eigenen Add-in

Diesen Monat möchte ich einen Aspekt der Programmentwicklung herausgreifen, der sich sehr schön zur Produktivitätssteigerung eignet. Häufig sieht man sich als Entwickler doch in der Situation, immer wieder dieselben Arbeiten ausführen zu müssen. Vor kurzem habe ich eine Reihe von Entwicklern gefragt, ob sie auch immer wieder dieselben ADO-Befehlssequenzen schreiben, mit denen irgendwelche Informationen aus der Datenbank ausgelesen werden sollen. Die meisten Befragten wussten sofort, was ich mit meiner Frage meinte.

Zur Vereinfachung der Entwicklung von Anwendungen, die mit ADO arbeiten und gespeicherte Prozeduren (stored procedures) benutzen, hat Microsoft das "ADO Stored Procedure Add-In for Visual Basic" entwickelt. Es generiert den ADO-Code, der für den Aufruf der von Ihnen ausgewählten gespeicherten Prozedur erforderlich ist. Das ist eine willkommene Hilfe, denn der Aufruf von gespeicherten Prozeduren via ADO ist doch etwas aufwendiger. Normalerweise muss man ein Command-Objekt einrichten und die entsprechende Parameters-Sammlung zusammenstellen.

Der genannte Assistent kann zwar sehr schön den ADO-Code generieren, aber wie sieht die Sache aus, wenn man für die Datenbankzugriffe eigene Objekte entwickelt hat?

Das Fitch & Mather-Beispielprogramm FMStocks eignet sich als gutes Beispiel (siehe http://www.fmstocks.com). Es hat eine ADOHelper-Klasse mit den verschiedenen Run-Funktionen, in denen die eigentliche Arbeit erledigt wird. Sie brauchen den fertigen Code nur noch aufzurufen. Aber selbst für den relativ einfachen Einsatz der Run-Funktionen müssen Sie immer noch die verschiedenen Parameter der gespeicherten Prozeduren kennen, samt Typ und Größe. Also dachte ich mir, es müsse doch eine einfachere Lösung für die Arbeit mit Datenbankobjekten geben.

Also entschloss ich mich, ein neues Modul (oder "Add-In") zu entwickeln, das den Aufruf der Run-Funktionen nach FMStocks-Art automatisiert. Ausgangspunkt sollte der ADO Stored Procedure-Assistent sein. Dieses Modul kann die Entwickler auf zweierlei Arten unterstützen. Erstens ermöglicht es die Auswahl der aufzurufenden gespeicherten Prozedur. Zweitens ermöglicht es die Auswahl einer Methode vom Datenbankobjekt und es bietet Ihnen die Möglichkeit, verschiedene Argumente für den Methodenaufruf festzulegen. Anschließend kann das Modul den entsprechenden Code in den Editor von Visual Basic einfügen, und zwar an der aktuellen Cursorposition. So können Sie praktisch in Sekunden die richtige Methode und gespeicherte Prozedur aufrufen.

Änderung des Programmbeispiels

Ich möchte nun die einzelnen Schritte beschreiben, in denen ich die Vorlage geändert habe. Nach dem Herunterladen des Codes von der Fitch & Mather-Site kopierte ich alle Dateien in einen neuen Ordner. Anschließend änderte ich den Namen der Projektdatei in SprocWizardMP.vbp ab. Dann öffnete ich das Projekt in Visual Basic und änderte den Projektnamen auf SprocWizardMP und die Projektbeschreibung auf "Stored Procedure MP Generator Wizard".

Nach dem Öffnen von frmAddin begann ich mit der Änderung der Anwenderschnittstelle. Bild B1 zeigt das fertige Formular. Das Textfeld in der oberen linken Ecke zeigt den Text GetDSN(). Damit ist die Funktion gemeint, die ich zur Ermittlung eines DSN-Strings benutze. Ihr Ergebniswert wird als letztes Argument an jede der Run-Methoden übergeben, so dass sich in jedem Aufruf der korrekte unverwechselbare DSN verwenden lässt. Die GetDSN-Funktion muss in dem Modul oder in der Klasse definiert werden, in welcher der Aufruf der Run-Methode erfolgt. Sie können selbst einen DSN-String vorgeben oder den aktuellen String mit der Funktion GetDSN ermitteln.

Bild01

B1 Das Zusatzmodul Ado Stored Procedure (ein "Add-in").

Das Listenfeld auf der rechten Seite des Formulars zeigt die Namen der Methoden, die das Datenbankobjekt anzubieten hat. Sie werden als statische Liste eingetragen. Ändern Sie diese Liste bei Bedarf für Ihre eigenen Zwecke ab, damit sie alle Funktionen anzeigt, die von Ihren Objekten angeboten werden.

Das Kombinationsfeld cboSproc erhält die Namen der gespeicherten Prozeduren dagegen dynamisch, und zwar von der Prozedur LoadSprocList (dazu gleich mehr). Im Kombinationsfeld cboDSNForSproc sind die DSNs zu finden, die zur Verbindungsaufnahme mit der Datenbank benutzt werden, in welcher die in cboSproc angegebenen gespeicherten Prozeduren zu finden sind. Dieses Steuerelement erhält seine Werte von der Prozedur LoadDSNList. Die DSNs liegen in der Datei DataAccess.txt unter c:\database. Der Dateiinhalt lautet:

Accounts DSN=Accounts 
Pubs DSN=Pubs 
SessionData  DSN=SessionData

Der erste Eintrag in jeder Zeile ist der DSN-Name, der im Kombinationsfeld angezeigt wird. Die zweite Angabe beginnt mit DSN= und nennt den eigentlichen DSN. Sie können einen DSN-Namen benutzen, wie ihn dieses Beispiel zeigt, oder einen vollständigen Verbindungsstring angeben.

Die erste Reihe mit den Schaltern wirkt sich auf den Code aus, der von dem Modul generiert wird. Je nach Bedarf haken Sie bei der Arbeit einfach die Punkte ab, die Sie interessieren. Manche gespeicherten Prozeduren liefern zum Beispiel Recordsets (Tabellen). In diesem Fall sollten Sie das Feld Returns Rows abhaken. Für gespeicherte Prozeduren, die nur einen Einzelwert liefern und keine Datensätze, sollte das Feld Include Return Parameter markiert werden und nicht Returns Rows.

Die Markierung des Felds Include Variable Declarations veranlasst das Modul, Dim-Anweisungen für den Recordset und die Ergebnisvariable zu generieren. Normalerweise erhält die Recordset-Variable den Namen rs. Er erscheint im ersten Textfeld auf der letzten Zeile. Das zweite Textfeld zeigt den Variablennamen für das Datenbankobjekt. Sie können diese Werte bei Bedarf ändern.

L1 So werden die Code-Details zusammengestellt

strRecordset = txtRecordset.Text 
strCode = "" 
If chkIncludeDeclare.Value Then 
strCode = strCode & "Dim " & strRecordset & _ 
      " as ADODB.Recordset" & vbCrLf 
      strCode = strCode & "Dim vReturn as variant" & vbCrLf 
End If 
If chkReturnsRows.Value Then 
strCode = strCode & "Set " & strRecordset & " = " & _ 
        txtDBObject.Text & "." & lstConnType.Text & "(" 
Else 
      strCode = strCode & "vReturn = " & txtDBObject.Text & "." _ 
        & lstConnType.Text & "(" 
End If

Die Zeile mit den Schaltflächen am unteren Rand des Dialogs ist für die Aktionen des Moduls zuständig. Die Apply-Schaltfläche generiert den Code an der aktuellen Cursorposition. Die zweite Schaltfläche namens Preview generiert den Code und bringt ihn in einem Vorschau-Formular unter. Mit Load Sprocs werden die Werte für das DSN-Kombinationsfeld geladen, während die Schaltfläche Load DSNs das DSN-Feld bestückt. Die Namen der gespeicherten Prozeduren werden bei der Auswahl eines DSNs automatisch geladen. Die DSNs werden automatisch geladen, wenn das erste Formular des Moduls geladen wird. Diese beiden Schaltflächen kommen dann zur Geltung, wenn entweder die DSNs oder die gespeicherten Prozeduren aktualisiert werden sollen, ohne das Modul neu laden zu müssen. Falls es zum Beispiel in der Datenbank neue gespeicherte Prozeduren gibt, können Sie deren Namen mit einem Klick auf die Schaltfläche Load Sprocs laden.

L2 Bearbeitung der Parameters-Sammlung

For Each param In params 
    If Not chkIncludeReturnParameter.Value = 1 Then 
        If param.Name = "@RETURN_VALUE" Then 
            GoTo forEnd 
        End If 
    End If 
    If Not bParams Then 
        strCode = strCode & "Array(" 
        strCode = strCode & "mp(" 
        bParams = True 
    Else 
        strCode = strCode & ", _ " & vbCrLf & "  mp(" 
    End If 
    strCode = strCode & Chr(34) & param.Name & Chr(34) & "," 
    Select Case param.Type 
        Case 3 
            strCode = strCode & "adInteger" 
        Case 129 
            strCode = strCode & "adChar" 
        Case 200 
            strCode = strCode & "adVarChar" 
        Case 128 
            strCode = strCode & "adBinary" 
        Case 11 
            strCode = strCode & "adBoolean" 
        Case Else 
            strCode = strCode & param.Type 
    End Select 
    strCode = strCode & ", " & param.Size 
    If Left$(param.Name, 1) = "@" Then 
        sParamName = Right(param.Name, Len(param.Name) - 1) 
    Else 
        sParamName = param.Name 
    End If 
    strCode = strCode & ", " & sParamName & ")" 
forEnd: 
    Next

In den Tiefen des Codes

Der Code in der Funktion GenADOCode erledigt fast die gesamte Arbeit, zu der dieses kleine Zusatzmodul fähig ist. Sie stellt den Text zusammen, der in den Editor von Visual Basic eingefügt werden soll, und gibt ihn als Ergebniswert an den Aufrufer zurück. Der Text wird in drei Variablen abgelegt. StrHeader enthält die Kopfzeilen für den Codeblock, strCode nimmt den eigentlichen Code auf und strFooter ist für die Fußzeilen zuständig.

Im ersten Teil des Codes werden die Variablen für die Prozedur dimensioniert. Anschließend definiert der Code die Strings für Kopf und Fuß:

strHeader = "' Sproc Wizard Code Generation START -" & _ 
    vbCrLf & vbCrLf 
strFooter = vbCrLf & "' Sproc Wizard Code Generation END -" _ 
    & vbCrLf

Beachten Sie bitte den Einsatz von vbCrLf. Dadurch werden beim Einfügen des Textes auch die Zeichen für Wagenrücklauf und Zeilenvorschub (CR/LF) in den Text eingebaut. Sie dienen zur Formatierungund beginnen jeweils eine neue Zeile.

Die nächsten Zeilen definieren die Properties für die ADO-Verbindung und stellen die Verbindung zu der Datenbank her, in der die gespeicherten Prozeduren liegen:

' Properties für Verbindung festlegen und Verbindung herstellen 
cn.CursorLocation = adUseClient 
cn.ConnectionString = gStrSprocDSN 
cn.Open

Sobald die Verbindung steht, richtet der Code ein Command-Objekt ein, um die gespeicherten Prozeduren aus dem Kombinationsfeld cboSproc benutzen zu können. Dann wird die Refresh-Methode des Parameters-Objekts aufgerufen. Sie ermittelt die Parameter der gespeicherten Prozeduren:

Set cmd.ActiveConnection = cn 
cmd.CommandText = cboSproc.Text 
cmd.CommandType = adCmdStoredProc 
cmd.Parameters.Refresh

Die nächste Codezeile setzt die Variable params als eine Referenz auf die Parameters-Sammlung. Diese Variable dient später zur Ermittlung der Parameter-Details:

Set params = cmd.Parameters

Der Code in Listing L1 beginnt mit der Zusammenstellung des Codes und legt das Ergebnis in der Variablen strCode ab. Nun sollte Ihnen der in strCode gespeicherte Code auch schon bekannt vorkommen. Es ist dieselbe Art von Code, die man für jedes Datenbankobjekt einsetzen würde. Die folgende Zeile hängt den Namen der gespeicherten Prozedur an strCode an:

strCode = strCode & Chr(34) & cboSproc.Text & Chr(34) & ","

Die Variable bParams wird auf False gesetzt und dient in einer For-Schleife dazu, die Ausgabe der Array-Funktion nach dem ersten Parameter anzuhalten.

bParams = False

Der Code in Listing L2 zeigt die For-Schleife, in der die Parameters-Sammlung bearbeitet wird. Jeder Parameter wird in dieser Schleife untersucht, indem bestimmte Properties überprüft werden, zum Beispiel Type und Name. Dann wird für jeden Parameter eine mp-Anweisung an strCode angehängt. In der Anweisung Select Case param.Type dürften noch einige Änderungen erforderlich werden, sofern Sie zusätzliche Datentypen implementieren möchten.

Nach dem Ende der For-Schleife schließt der Code die Zusammenstellung von strCode ab, indem er die abschließende runde Klammer einbaut, sofern irgendwelche Parameter ausgegeben wurden. Bei Bedarf fügt er die DSN-Anweisung hinzu und schließt dann die Verbindung. Damit wäre strCode abgeschlossen und der Code setzt den Rückgabewert auf strCode.

If bParams Then 
    strCode = strCode & ")" 
End If 
If chkIncludeDSN.Value Then 
    strCode = strCode & ", _" & vbCrLf & "  " & _ 
      txtConnectionString.Text & ")" & vbCrLf 
Else 
    strCode = strCode & ")" & vbCrLf 
End If 
cn.Close 
GenADOCode = strHeader & strCode & strFooter 

Die Funktion mp (Make Parameter), die bei jedem Parameter benutzt wird, stammt aus FMStocks und dient zur Konstruktion eines Parameter-Arrays für jeden Parameter. Die Funktion sieht so aus:

Public Function mp(ByVal PName As String, _     
    ByVal PType As ADODB.DataTypeEnum, _ 
    ByVal PSize As Long, ByVal PValue As Variant) 
    mp = Array(PName, PType, PSize, PValue) 
End Function

Diese Funktion muss zusammen mit der DSN-Funktion in jedes Projekt eingebaut werden, das die Ergebnisse dieses Zusatzmoduls verwendet. Am einfachsten dürfte es wohl sein, die Datei Helpers.bas aus FMStocks 2000 zu verwenden (siehe https://msdn.microsoft.com/library/techart/fm2kintro.htm).

Bild02

B2 Einbau von Verweisen.

Noch mehr Code

Das Zusatzmodul fügt sich ins Objektmodell von Visual Basic 6.0 ein, ebenso in die Add-in-Verbindung. Dazu legt es die entsprechenden Referenzen (Verweise) an (Bild B2) und deklariert die beiden folgenden öffentlichen Variablen:

Public VBInstance As VBIDE.VBE 
Public Connect As Connect

Diese beiden Deklarationen liegen im Allgemein-Bereich von frmAddIn. Dadurch werden diese Referenzen im gesamten Zusatzmodul verfügbar. Bild B1 zeigt die Verweise, die in diesem Projekt benutzt werden.

Das Zusatzmodul arbeitet nur an einer Stelle mit der IDE von Visual Basic zusammen, nämlich im Codefenster. Nachdem Sie eine Methode und eine gespeicherte Prozedur ausgewählt haben, führt ein Klick auf die Apply-Schaltfläche dazu, dass der generierte Code an der aktuellen Cursorposition ins Codefenster eingefügt wird. Wenn Sie Apply anklicken, wird die Ereignisprozedur cmdApply_Click aufgerufen. Die ersten vier Zeilen dieser Prozedur definieren die benutzen Variablen:

Dim lngStartLine As Long 
Dim lngStartCol As Long 
Dim lngEndLine As Long 
Dim lngEndCol As Long

Die nächste Zeile ruft die Methode GetSelection auf und besorgt auf diese Weise Informationen darüber, ob und welcher Teil des Codes im Codefenster markiert ist. Das Zusatzmodul braucht die aktuelle Zeile, damit es den Code an der richtigen Stelle einfügen kann. Die aktuelle Zeile geht aus der Variablen IngStartLine hervor:

VBInstance.ActiveCodePane.CodeModule.CodePane.GetSelection _ 
    lngStartLine, lngStartCol, lngEndLine, lngEndCol

Die folgende Zeile generiert den Code und fügt ihn ins Codefenster ein:

VBInstance.ActiveCodePane.CodeModul.InsertLines _ 
    lngStartLine, GenADOCode()

GenADOCode taucht hier als letztes Argument der Methode InsertLines auf.

Falls der Entwickler die Preview-Schaltfläche statt Apply anklickt, wird der generierte Code von der Ereignisprozedur cmdPreview_Click ans Preview-Formular geschickt.

Die Arbeit mit dem neuen Zusatzmodul

Der Umgang mit dem Zusatzmodul ist ziemlich einfach. Wählen Sie es im Add-In-Menü und suchen Sie den DSN aus, der die richtige Datenbank bezeichnet. Wählen Sie die gewünschte gespeicherte Prozedur, legen Sie die erforderlichen Optionen fest und klicken Sie dann auf Apply. Der Code wird direkt an der aktuellen Cursorposition in den Editor eingefügt.

Die folgenden Zeilen stammen aus einer Testklasse, an der ich das Modul ausprobiert habe:

Dim oDB As BestISVDBUtil.DBHelper 
Set oDB = CreateObject("BestISVDBUtil.DBHelper") 
' Sproc Wizard Code Generation START --------- 
Dim rs As ADODB.Recordset 
Dim vReturn As Variant 
Set rs = oDB.RunSPReturnRS("GetUserByLastName", _ 
    Array(mp("@lastnamemask", adVarChar, 30, lastnamemask)), _  
      GetDSN()) 
' Sproc Wizard Code Generation END --------- 
Set tester2 = rs

Die ersten beiden Zeilen und die letzte Zeile stammen von mir. Die anderen stammen vom Codegenerator, einschließlich der beiden Kommentarzeilen (Außerdem muss sich natürlich die Formatierung den Regeln anpassen, die hier für die gedruckte Seite gelten.).

Interessant sind die Aufrufe von mp und GetDSN in der Zeile, die mit Set rs beginnt. Wie schon erwähnt, müssen Sie auch diese beiden Funktionen in Ihren Code einbauen.

Fazit

Die Erstellung von kleinen Zusatzmodulen oder "Add-Ins" ist eine Möglichkeit, die eigene Produktivität als Entwickler zu verbessern. Die entsprechende Automatisierung häufig anfallender Arbeitsschritte verkürzt nicht nur die reine Entwicklungszeit, weil der Codegenerator das Programm wieder ein paar Zeilen voran bringt, sondern verringert auch die Zahl der Fehlerquellen. Vermutlich werden Sie auch etwas weniger Zeit im Debugger verbringen müssen.

Es bieten sich noch viele andere Arten von Zusatzmodulen an, die auf ihre Entwicklung harren. So könnten Sie zum Beispiel ein Modul entwickeln, dass es Ihnen erlaubt, im Web herumzustöbern, während Sie an der Visual Basic-Anwendung arbeiten. Sie könnte auch für die anderen Geschäftsobjekte in Ihrer Sammlung entsprechende Codegeneratoren entwickeln. Dieses Thema wird uns wohl auch in Zukunft hin und wieder beschäftigen.