6 Connection-Objekt (Verbindungsaufbau)

Werbung
6
Connection-Objekt
(Verbindungsaufbau)
Bevor ein VB-Programm Daten abfragen oder verändern kann, muß es
eine Verbindung zum Datenbank-Server herstellen. Im Einführungsbeispiel in Kapitel 1 wurde dazu ein Connection-Objekt des DataEnvironmentDesigners verwendet. Das ist aber bei weitem nicht die einzige Möglichkeit.
Dieses Kapitel gibt einen Überblick über mögliche Strategien zur Herstellung der Verbindung. Es verrät auch, wie Probleme beim Verbindungsaufbau festgestellt und eventuell auch behoben werden können.
6.1
6.2
6.2.1
6.2.2
6.2.3
6.3
6.3.1
6.3.2
6.3.3
6.3.4
6.3.5
6.4
6.4.1
6.4.2
6.4.3
6.5
6.5.1
6.5.2
6.5.3
6.5.4
6.6
6.7
6.7.1
6.7.2
6.8
6.9
6.10
Grundlagen
Einführungsbeispiele
ADO-Code
Adodc
DataEnvironment
ConnectionString-Parameter
Allgemeine Verbindungsinformationen
Auswahl des OLE-DB-Treibers (Provider)
Einstellungen für den SQL-Server-Treiber SQLOLEDB
Einstellungen für den ODBC-Treiber MSDASQL
Einstellungen für den Jet-Treiber
Eigenschaften des Connection-Objekts
Connection- und Adodc-Eigenschaften
DEConnection-Eigenschaften
Zusätzliche Eigenschaften (Properties)
Fehlerabsicherung
ADO-Code (SQLOLEDB)
ADO-Code (MSDASQL/ODBC)
Adodc (SQLOLEDB)
DataEnvironment (SQLOLEDB)
Asynchroner Verbindungsaufbau
DataLink-Dateien
DataLink-Dateien in VB-Programmen nutzen
OLE DB Service Component Library (MSDASC)
Transaktionen
Errors-Aufzählung
Verbindungs- und Ressourcen-Verwaltung
184
185
186
187
189
190
191
194
195
200
203
204
205
206
207
207
208
211
213
216
221
226
226
228
231
234
239
184
6.1
6 Connection-Objekt (Verbindungsaufbau)
Grundlagen
Im Prinzip gibt es folgende Möglichkeiten, in einem VB-Programm auf ADO-Basis
eine Verbindung zur Datenbank herzustellen:
• Per Programmcode: Sie übergeben die Verbindungsinformationen direkt an die
Eigenschaften entsprechender ADO-Objekte (etwa Connection oder Recordset).
• Durch das Datenbankfeld Adodc: Die Verbindungsinformationen werden während der Programmentwicklung im Eigenschaftsfenster eingestellt. In der Folge gilt
das Adodc-Feld dann für andere verbundene Steuerelemente als Datenquelle. Ein
Vorteil dieses Steuerelements besteht darin, daß es vier Navigationsbuttons vorsieht, die oft benötigt werden – d.h., Sie sparen sich eventuell ein paar Zeilen Code.
• Durch ein Connection-Objekt des DataEnvironment-Designers: Abermals werden die Verbindungsinformationen schon während der Programmentwicklung in
einem komfortablen Dialog eingestellt. Der Vorteil gegenüber Adodc besteht darin,
daß das Connection-Objekt zentral im gesamten Programm verwendet werden
kann. (Im Gegensatz dazu stellt Adodc nur den Zugang zu einem einzigen Recordset
her. Im Regelfall benötigt ein Programm daher eine ganze Menge Adodc-Felder.)
Bevor es ins Detail geht, kurz noch eine Bewertung: Die Codevariante ist am flexibelsten, sie ist aber auch mit dem größten Programmieraufwand verbunden. (Wie sich
zeigen wird, ist der Programmieraufwand nicht viel größer, weil beinahe derselbe
Code bei den anderen Varianten zur Fehlerabsicherung erforderlich ist.)
ANMERKUNG
Bei der Adodc-Variante ist es schwer, Vorteile zu erkennen (von den vier Navigationsbuttons mal abgesehen). Es ist aus nicht nachvollziehbaren Gründen inkompatibel mit
dem DataEnvironment-Designer (d.h., dieser Designer kann nicht für das Adodc-Feld
als Datenquelle dienen). Eine Steuerung der Navigation via Tastatur ist nicht vorgesehen. Wenn es überhaupt eine vernünftige Einsatzmöglichkeit dieses Steuerelements
gibt, dann wohl für Mini-Clients, die aus nicht viel mehr als einem Datenformular bestehen.
Das Adodc-Feld ist der Nachfolger des Data-Feldes (DAO-Code), das bei professionellen VB-Programmierern keinen guten Ruf genießt, nicht zuletzt wegen
des verschwenderischen Umgangs mit Ressourcen und der komplizierten Fehlerabsicherung. Die ersten Erfahrungen mit dem neuen Steuerelement haben
wenig dazu beigetragen, die anfängliche Skepsis (die man auch als Vorurteil
bezeichnen könnte) zu mindern. Kurz und gut: Das Adodc-Feld spielt in diesem
Buch eine untergeordnete Rolle.
Die Gründe zum Einsatz des DataEnvironment-Designers liegen nicht beim ConnectionObjekt, sondern vielmehr beim Command-Objekt. Dieses erleichtert den Zugang auf
Tabellen, Sichten und SQL-Prozeduren sowie den Entwurf neuer Abfragen derart, daß
Sie in einer umfangreichen Anwendung kaum darauf verzichten wollen.
6.2 Einführungsbeispiele
185
Der Nachteil liegt wo anders: der Designer (in Kombination mit dem Datenansichtfenster) ist in einigen Details unausgegoren. Bis Sie sich mit seinen Mängeln schließlich
abgefunden und arrangiert haben, wird Ihnen vermutlich das eine oder andere graue
Haar gewachsen sein. (Dieses Buch wird versuchen, Ihre vorzeitige Alterung so gut
wie möglich zu verhindern ... )
Die meisten Beispiele dieses Buchs basieren auf dem DataEnvironment-Designer, was
man in gewisser Weise als persönliche Wertung betrachten kann. Zumindest dem
Autor des Buchs ist es also letzlich ganz gut gelungen, sich mit dem Designer anzufreunden.
6.2
Einführungsbeispiele
Im folgenden werden drei gleichwertige Beispielprogramme vorgestellt. Jedes dieser
Programme ermöglicht es, durch die alphabetisch geordnete Liste der pubs-Verlage zu
blättern. Alle drei Beispiele verwenden mars als Datenbank-Server und eine vertraute
Verbindung (NT-Sicherheit).
Wenn der Server nicht gefunden wird oder wenn Sie keine Zugriffsrechte auf die Datenbank haben, kommt es zu Fehlern, die aber nicht abgefangen werden (das ist Thema eines eigenen Abschnitts ab Seite 207). Damit Sie die Beispielprogramme auf Ihrem
Rechner ausführen können, müssen Sie also in allen drei Fällen den Server-Namen
einstellen. Die Änderung betrifft im ersten Beispiel den Programmcode zur Einstellung der ConnectionString-Eigenschaft, im zweiten Beispiel dieselbe Eigenschaft des
Adodc-Steuerelements und im dritten Beispiel die Einstellung des Connection-Objekts
des DataEnvironment-Designers.
VERWEIS
Bild 6.1: Rein optisch unterscheiden sich die drei Beispiele kaum
Ein bißchen sind die Beispiele schon ein Vorgriff auf die weiteren Kapitel, die
sich ausführlich mit Datensatzlisten und dem ADO-Recordset-Objekt beschäftigen – aber das läßt sich leider nicht vermeiden. Wenn Sie Verständnisprobleme
mit dem Code zur Navigation im Datensatz haben, ignorieren Sie diese vorerst
– oder blättern Sie vor zu Seite 241.
186
6 Connection-Objekt (Verbindungsaufbau)
6.2.1
ADO-Code
Die Codevariante ist am einfachsten zu dokumentieren. Außer einem Textfeld (txtPubl) und vier Buttons (Steuerelementfeld cmdNavigate) bedarf es lediglich des folgenden Codes. Es brauchen keine Eigenschaften eingestellt zu werden, es müssen keine
Designer aufgerufen werden etc.!
Einige Anmerkungen zum Code: In Form_Load wird zuerst ein ADO-Connection-Objekt erzeugt. Nach der Einstellung der ConnectionString-Eigenschaft wird die Verbindung zur Datenbank hergestellt. (Die Syntax von ConnectionString wird übrigens in
einem eigenen Abschnitt ab Seite 190 beschrieben.)
Im zweiten Schritt wird die Abfrage SELECT pub_name FROM publishers ORDER BY
pub_name durchgeführt. Dazu wird ein Recordset-Objekt zu Hilfe genommen, über das
anschließend die Ergebnisse der Abfrage gelesen werden.
Schließlich wird das Textfeld txtPubl mit dem Recordset-Objekt verbunden (Eigenschaften DateSource und DataField), damit darin die Abfrageergebnisse ohne weiteren
Codeaufwand angezeigt werden.
HINWEIS
' Connection\ADO-Code\Intro\Intro.vbp
Dim conn As Connection
Dim rec As Recordset
Private Sub Form_Load()
Set conn = New Connection
conn.ConnectionString = _
"Provider=SQLOLEDB.1;Integrated Security=SSPI;" & _
"Initial Catalog=pubs;Data Source=MARS"
conn.Open
Set rec = New Recordset
Set rec.ActiveConnection = conn
rec.CursorType = adOpenKeyset
rec.Source = "SELECT pub_name FROM publishers ORDER BY pub_name"
rec.Open
Set txtPubl.DataSource = rec
txtPubl.DataField = "pub_name"
End Sub
Im Gegensatz zu anderen Datenbankbibliotheken kann bei ADO ein RecordsetObjekt erzeugt werden, ohne es von einem vorher erzeugen Connection-Objekt
abzuleiten. In der Praxis ist das aber selten sinnvoll. Meist werden nämlich
mehrere Recordset-Objekte benötigt, die dann alle auf dem gleichen ConnectionObjekt basieren können. Dadurch wird nicht nur der Programmcode übersichtlich und kompakt gehalten, sondern auch einen unnötigen Ressourcenverbrauch vermieden.
6.2 Einführungsbeispiele
187
Der Code zur Navigation in der Datensatzliste ist minimalistisch (vor allem die Fehlerabsicherung!), aber zur Demonstration ausreichend. Im Prinzip geht es lediglich
darum, die Methoden MoveFirst, -Next etc. für das Recordset-Objekt auszuführen. (Dabei können Fehler auftreten, wenn versucht wird, über das Ende der Datensatzliste
hinaus zu springen.)
TIP
Denken Sie daran, daß Sie die ADO-Bibliothek explizit aktivieren müssen (PROJEKT|VERWEISE) – sonst stehen die ADO-Objekte Connection und Recordset nicht
zur Verfügung. Bei den beiden anderen Beispielen erfolgt das automatisch, sobald Sie das Adodc-Steuerelement einfügen bzw. sobald Sie den DataEnvironment-Designer aktivieren.
TIP
Private Sub cmdNavigate_Click(Index As Integer)
On Error Resume Next
With rec
Select Case Index
Case 0: .MoveFirst
Case 1: .MovePrevious
Case 2: .MoveNext
Case 3: .MoveLast
End Select
End With
If Err Then Beep
End Sub
Die Navigationsbuttons des Beispielprogramms sind in ihrer Gestaltung ein
wenig lieblos. Zur Darstellung der Pfeile wurden einfach die drei Zeichen < >
und | eingesetzt. Wenn Sie schönere Pfeile haben möchten, müssen Sie in die
Picture-Eigenschaft der Buttons entsprechende Bitmaps laden. Wenn Sie keine
geeigneten Bitmaps finden und auch keine Lust haben, selbst welche zu zeichnen, können Sie auch den Datenformularassistenten einsetzen und dessen Buttons in Ihr eigenes Projekt kopieren. (Der Assistent erzeugt Buttons mit schönen
Bitmaps.)
6.2.2
Adodc
Das Adodc-Beispiel ist insofern bemerkenswert, als es ohne eine einzige Zeile datenbankspezifischen Code auskommt! (Die nicht abgedruckte cmdEnd-Ereignisprozedur
für den Programmende-Button ist trivial und hat nichts mit Datenbanken zu tun.)
Dafür müssen natürlich einige Eigenschaften eingestellt werden. Für das AdodcSteuerelement ist das zuerst einmal die Zeichenkette ConnectionString, die die eigentlichen Verbindungsinformationen enthält. Als Einstellung kann dieselbe Zeichenkette
188
6 Connection-Objekt (Verbindungsaufbau)
wie im Programm oben verwendet werden. In der Regel werden Sie zur Einstellung
aber den dafür vorgesehenen Dialog verwenden, d.h., ein paar Mausklicks reichen
aus.
Die zweite wichtige Eigenschaft ist RecordSource. Auch hier gibt es zur Einstellung
einen Dialog, in dem Sie als Befehlstyp adCmdUnknown angeben. Anschließend müssen Sie dasselbe SQL-Kommando wie im vorangegangenen Beispiel eingeben. Mit
Ende des Dialogs wird das SQL-Kommando in RecordSource gespeichert, der Befehlstyp in CommandType. Anschließlich sollten Sie noch die Mode-Eigenschaft auf adModeRead stellen, um eine unbeabsichtige Veränderung von Daten zu vermeiden.
Nachdem das Adodc-Feld nun vollständig initialisiert ist, muß es mit dem Textfeld
verbunden werden. Dazu stellen Sie im Eigenschaftsfenster die DataSource auf Adodc1
(oder wie immer Sie das Adodc-Steuerelement benannt haben) und DataField auf pub_name.
Adodc und die ADO-2.1-Bibliothek
Sobald Sie das Adodc-Steuerelement in die Toolbox aufnehmen, wird die ADO-Bibliothek 2.0 aktiviert – auch dann, wenn am Rechner bereits ADO 2.1 installiert ist! Wenn
Sie also die aktuelle ADO-Bibliothek verwenden möchten, müssen Sie diese via
PROJEKT|VERWEISE selbst auswählen. (Das gilt auch für das folgende DataEnvironmentBeispiel.)
Allerdings gibt es Kompatibilitätsprobleme zwischen Adodc (das auf der Basis von
ADO 2.0 kompiliert wurde) und neueren ADO-Bibliotheken. Diese Probleme bleiben
meist unbemerkt, bis Sie versuchen, eine Adodc-Ereignisprozedur zu erstellen, in der
als Parameter ein Recordset-Objekt übergeben wird. VB meldet sich dann beim Kompilieren mit der Meldung: Deklaration der Prozedur entspricht nicht der Beschreibung des
Ereignisses. Der Hintergrund: Die interne Definition des Recordset-Objekts hat sich mit
ADO 2.1 geändert; die Adodc-Ereignisprozedur erwartet aber ein Recordset-Objekt
gemäß der Definition von ADO 2.0. (Noch detaillierter wird das Verhalten im KnowledgeBase-Artikel Q222145 beschrieben.)
Bis Microsoft eine Visual-Basic-Version mit einem aktualisierten Adodc-Steuerelement
liefert (was mit einem Service Pack an sich möglich wäre, in Q222145 aber erst für die
nächste VB-Version versprochen wird), ist folgende Abhilfe notwendig: Sie verändern
in der Ereignisprozedur den Datentyp des Recordset-Objekts zu Recordset20.
Private
ByVal
ByVal
ByVal
Sub Adodc1_MoveComplete( _
adReason As ADODB.EventReasonEnum,
pError As ADODB.Error, adStatus As ADODB.EventStatusEnum, _
pRecordset As ADODB.Recordset20)
Damit wird trotz ADO 2.1 ein altes Recordset-Objekt verwendet, das kompatibel zu
ADO 2.0 ist. Übrigens entdecken Sie neben Recordset20 eine ganze Reihe weiterer abwärtskompatibler Objekte (sowohl für ADO 1.5 als auch für ADO 2.0), sobald Sie im
6.2 Einführungsbeispiele
189
Objektkatalog das Kontextmenükommando AUSGEBLENDETE MITGLIEDER ANZEIGEN ausführen.
6.2.3
DataEnvironment
Auch hier gibt es wenig Code, aber viel Einstellarbeit, die im DataEnvironment-Designer beginnt. Dort wird ein Connection-Objekt erzeugt, das die Verbindung zur Datenbank herstellt, und ein Command-Objekt mit dem nun schon bekannten SQL-Kommando. (Eine Einführung in den Umgang mit dem DataEnvironment-Designer gibt Kapitel
1.)
Im Formular müssen folgende Eigenschaften für das Textfeld eingestellt werden, damit im Textfeld tatsächlich ein Name der alphabetischen Verlagsliste angezeigt wird:
DataSource = DE
DataMember = commPublishers
DataField = pub_name
Kurz noch ein Überblick über die wichtigsten Objektnamen:
DE
connPubs
commPublishers
txtPubl
cmdNavigate
Wurzelobjekt des DataEnvironment-Designer
Connection-Objekt des DataEnvironment-Designer
Command-Objekt des DataEnvironment-Designer
Textfeld im Formular
Navigations-Buttons im Formular (Steuerelementfeld)
Der Code entspricht fast exakt dem des ADO-Codebeispiels. Der einzige Unterschied
besteht darin, daß die Navigation in der Datensatzliste über das rscommPublishersObjekt erfolgt. Dieses Objekt wird vom DataEnvironment-Designer automatisch als
Ergänzung zu commPublishers zur Verfügung gestellt. (Die Anfangsbuchstaben rs
deuten darauf hin, daß es sich um ein Recordset-Objekt handelt.)
' Connection\DataEnvironment\Intro\Intro.vbp
Private Sub cmdNavigate_Click(Index As Integer)
On Error Resume Next
With DE.rscommPublishers
Select Case Index
Case 0: .MoveFirst
Case 1: .MovePrevious
Case 2: .MoveNext
Case 3: .MoveLast
End Select
End With
If Err Then Beep
End Sub
190
6 Connection-Objekt (Verbindungsaufbau)
DataEnvironment-Code und die ADO-2.1-Bibliothek
Wie das Adodc-Steuerelement hat auch der DataEnvironment-Designer noch nichts von
ADO 2.1 gehört. Daraus resultieren zum Glück selten Probleme. Eine Ausnahme stellen aber Ereignisprozeduren dar, an die ein Recordset-Objekt übergeben wird. Diese
Prozeduren erwarten Recordsets gemäß ADO 2.0. Daher muß der Recordset-Parameter
entsprechend deklariert werden:
Private
ByVal
ByVal
ByVal
ByVal
Sub connXyz_ExecuteComplete(ByVal RecordsAffected As Long,
pError As ADODB.Error, adStatus As ADODB.EventStatusEnum,
pCommand As ADODB.Command,
pRecordset As ADODB.Recordset20,
pConnection As ADODB.Connection)
Weitere Details zu den Hintergründen lesen Sie bitte im vorangegangenen Abschnitt
zum Adodc-Steuerelement nach.
6.3
ConnectionString-Parameter
Allen drei oben beschriebenen Varianten, also das Connection-Objekt der ADOBibliothek, das Adodc-Steuerelement und das Connection-Objekt des DataEnvironmentDesigners, haben eines gemeinsam: die ConnectionString-Eigenschaft. Diese Eigenschaft steuert den Verbindungsaufbau und die Eigenschaften der Verbindung.
Bild 6.2: Der Datalink-Dialog zeigt viele
ConnectionString-Komponenten
6.3 ConnectionString-Parameter
191
Direkt in Berührung kommen Sie damit nur, wenn Sie der Eigenschaft im Programmcode eine Zeichenkette zuweisen müssen – sei es, weil Sie direkt mit ADOCode arbeiten möchten (ohne Designer bzw. Adodc-Steuerelement), sei es, weil Sie
Ihren Code gegen Verbindungsprobleme absichern möchten.
ANMERKUNG
Dieser Abschnitt versucht, die zahlreichen, oft optionalen Bestandteile der ConnectionString-Zeichenkette (siehe auch Bild 6.2) so gut zu beschreiben, wie es
eben geht. Die Ausgangsbedingungen sind freilich schwierig:
Die Dokumentation zu den ConnectionString-Parametern ist nämlich ebenso
chaotisch wie unvollständig. Da, wo überhaupt eine Beschreibung eines Parameters zu finden ist, widerspricht sie oft dem tatsächlichen Verhalten. Die
Schreibweisen der Parameter wurden anscheinend willkürlich gewählt (meistens mit Leerzeichen, manchmal ohne, manchmal mit _, manchmal nur Großbuchstaben, manchmal GeMischt etc.). Manche Parameter, die vor dem Verbindungsaufbau angegeben werden müssen, haben nach dem Verbindungsaufbau
neue Namen oder andere Werte. Manche Ungereimtheiten werden wohl im
Laufe der Zeit beseitigt werden – dann ist die folgende, oft empirisch ermittelte
Zustandsbeschreibung aber nicht mehr korrekt.
HINWEIS
Also: obwohl in diesem Abschnitt viel mehr Zeit steckt, als Sie vielleicht vermuten – erwarten Sie keine Wunder.
Der Begriff DataLink (deutsch Datenverknüpfung) ist ein Synonym für die Verbindungsinformationen, die in ConnectionString enthalten sind. Microsoft hat
die Möglichkeit vorgesehen, diese Verbindungsinformationen in sogenannten
Data-Link-Datei mit der Kennung *.udl zu speichern. Leider ist diese Möglichkeit schlecht dokumentiert. Abschnitt 6.7 ab Seite 226 versucht dieses Manko so gut wie möglich zu beheben.
6.3.1
Allgemeine Verbindungsinformationen
Die allgemeine Syntax der ConnectionString-Zeichenkette sieht folgendermaßen aus:
conn.ConnectionString = _
"Schlüsselwort1=Wert1;Schlüsselwort2=Wert2 ..."
Also zum Beispiel:
conn.ConnectionString = _
"Provider=SQLOLEDB.1;Initial Catalog=pubs;Data Source=MARS"
Die einzelnen Einträge sind durch Strichpunkte getrennt. Am Ende der Zeichenkette
muß kein Strichpunkt angegeben werden. Wertzuweisungen erfolgen – auch wenn es
sich um Zeichenketten handelt – ohne Apostrophe. Sowohl in den Schlüsselwörtern
192
6 Connection-Objekt (Verbindungsaufbau)
als auch in den Werten sind Leerzeichen erlaubt. (Anfang und Ende sind durch die
Zeichen = und ; vorgegeben.)
Die Zeichenkette wird zumeist als Ganzes der ConnectionString-Eigenschaft zugewiesen. Über die Properties-Eigenschaft des Connection-Objekts ist aber auch eine bequeme
Veränderung einzelner Komponenten möglich, ohne den Rest der Zeichenkette zu
verändern. Die folgende Anweisung verändert also nur den Data-Source-Bestandteil
und stellt einen anderen Server-Namen ein.
VORSICHT
conn.Properties("Data Source") = "URANUS"
Wenn Sie mit ODBC-Datenquellen arbeiten, funktioniert Properties nur für einen Teil der ConnectionString-Parameter. Der Grund: einige ODBC-spezifischen
Parameter werden zu einem einzigen Parameter Extended Properties zusammengefaßt.
Nachdem ConnectionString direkt oder über den Umweg von Properties eingestellt
wurde, wird die Datenbankverbindung mit Open hergestellt. Wenn Sie ConnectionString anschließend nochmals ansehen, werden Sie feststellen, daß die Zeichenkette
jetzt viel länger ist; sie wurde um diverse Defaulteinstellungen des Treibers erweitert.
conn.Open
Debug.Print conn.ConnectionString
ConnectionString-Schlüsselwörter
TIP
Es gibt zwei Typen von Schlüsselwörtern in ConnectionString: allgemeine, die durch
die OLE DB bzw. durch ADO vorgegeben sind, und spezielle, die durch den jeweiligen OLE-DB-Provider definiert sind. Aus diesem Grund ist die Dokumentation
der Schlüsselwörter über die gesamte MSDN-Library verstreut.
Beachten Sie bitte, daß zwar viele, aber durchaus nicht alle Merkmale einer
Verbindung mit der ConnectionString-Zeichenkette gesteuert werden! Das
ADO-Connection-Objekt, das DEConnection-Objekt des DataEnvironment-Designers und das Adodc-Steuerelement weisen weitere Eigenschaften auf, die direkt
eingestellt werden müssen. (In der Entwicklungsumgebung erfolgt die Einstellung nicht im DATALINK-Dialog, sondern im Eigenschaftsfenster.) Eine kurze
Beschreibung dieser zusätzlichen Eigenschaften finden Sie ab Seite 204.
6.3 ConnectionString-Parameter
193
Bild 6.3: Zusatzeigenschaften des DEConnection-Objekts
Eine Syntaxzusammenfassung der in diesem Buch beschriebenen ConnectionString-Parameter finden Sie auf Seite 564. Einen Überblick über die wichtigsten
Schlüsselwörter finden Sie auch in der MSDN-Library (alle Angaben relativ zu
PLATFORM SDK|DATA ACCESS SERVICES):
-DATA ACCESS SDK|ADO PROGRAMMER'S REFERENCE|USING PROVIDERS WITH ADO
Allgemeine OLE-DB- bzw. ADO-Schlüsselwörter sind hier beschrieben:
VERWEIS
- DATA ACCESS SDK|OLE DB|DATA LINK API REFERENCE
SQL-Server-spezifische Schlüsselwörter für OLE DB finden Sie im KnowledgeBase-Artikel Q193135 sowie hier:
- SQL SERVER PROGRAMMER'S TOOLKIT|BUILDING SQL SERVER APPLIC.|ADO AND SQL SERVER|ADO OBJECT MODEL|CONNECTION OBJECT
- SQL SERVER PROGRAMMER'S TOOLKIT|BUILDING SQL SERVER APPLIC.|OLE DB AND SQL SERVER|PROGRAMMING OLE DB APPLIC.|DATA SOURCE OBJECTS|INITIALIZATION
SQL-Server-spezifische Schlüsselwörter für ODBC sind schließlich im allerletzten Winkel der MSDN-Library versteckt:
- SQL SERVER PROGRAMMER'S TOOLKIT|BUILDING SQL SERVER APPLIC.|ODBC AND SQL SERVER|ODBC PROGRAMMER'S REFERENCE|API DETAILS|SQLCONFIGDATASOURCE
194
6 Connection-Objekt (Verbindungsaufbau)
6.3.2
Auswahl des OLE-DB-Treibers (Provider)
Das wichtigste ConnectionString-Schlüsselwort lautet ohne Zweifel Provider. Es gibt an,
welchen der verfügbaren OLE-DB-Datenbanktreiber Sie verwenden möchten. Alle
weiteren Parameter richten sich nach diesem Treiber. (Wenn die Provider-Angabe
fehlt, wird automatisch der ODBC-Treiber für OLE DB verwendet – siehe ab Seite
200.) Zur Auswahl stehen unter anderem:
ANMERKUNG
Provider=SQLOLEDB.1
Provider=MSDAORA.1
Provider=MSDASQL.1
Provider=Microsoft.Jet.OLEDB.3.51
Provider=Microsoft.Jet.OLEDB.4.0
Provider=MSDataShape
SQL-Server-Treiber
Oracle-Treiber
ODBC-Treiber
Jet-Engine-Treiber 3.51
Jet-Engine-Treiber 4.0
Treiber zum Umgang mit hierarchischen
Recordsets (siehe Seite 325)
Die Angabe der Versionsnummer ist optional. Wenn Sie fehlt, wird der neueste
installierte Treiber verwendet. Wenn Sie Ihr Programm mit einer älteren Version entwickelt haben, kann es dabei zu Inkompatibilitäten kommen. Es ist nicht
dokumentiert, was passiert, wenn im ConnectionString eine ältere Treiberversion verlangt wird, als die, die am Rechner installiert ist. Experimente mit der
Angabe nicht existenter Versionsnummern ergaben kein klares Bild. Es ist also
schwer zu sagen, ob es besser ist, die Versionsnummer anzugeben oder nicht.
Weder die MSDN-Library noch die dem Autor bekannte Literatur gibt dazu
Empfehlungen.
Verbindung zum SQL-Server: SQLOLEDB versus MSDASQL
Beachten Sie bitte, daß es zwei fundamental verschiedene Möglichkeiten gibt, auf den
SQL Server zuzugreifen!
• Die erste Variante liegt auf die Hand: Sie verwenden den für den SQL Server vorgesehenen Treiber SQLOLEDB (also ADO → OLE DB → SQL Server).
• Die Alternative ist der ODBC-Treiber MSDASQL. Da der SQL Server nicht nur
OLE DB, sondern auch ODBC unterstützt, ist der Umweg ADO → OLE DB →
ODBC → SQL Server möglich. (Die ConnectionString-Zeichenkette beginnt jetzt mit
"Provider=MSDASQL;Driver=SQL Server".)
Auf den ersten Blick werden Sie sagen, dieser zusätzliche Zwischenschritt kann die
Kommunikation nur noch langsamer und komplizierter machen. Prinzipiell haben Sie
recht – aber eben nur prinzipiell! Während der OLE-DB-Treiber für den SQL Server
ganz neu ist (und das ist immer bedenklich!), steckt im ODBC-Treiber jahrelange Erfahrung und Optimierung.
6.3 ConnectionString-Parameter
195
Des weiteren gibt es einzelne Funktionen, die im OLE-DB-Treiber noch fehlen. So verrät die ADO PROGRAMMER'S REFERENCE|USING PROVIDERS WITH ADO gleichsam in einer
Randnotiz, daß SQLOLEDB keine multiple rowsets unterstützt. (Das sind – zum Glück
selten vorkommende – SQL-Kommandos, die nicht eine, sondern gleich mehrere Datensatzlisten als Ergebnis liefern. ADO sieht die Methode NextRecordset zur Auswertung solcher Ergebnisse vor – aber das hilft nichts, wenn der OLE-DB-Treiber solche
Ergebnisse gar nicht erst weiterleitet.)
William Vaugh, dank guter Kontakte zu Microsoft-Entwicklern quasi der Papst zum
Thema SQL Server und VB, meint auf S. 672 seines Hitchhiker's Guide lakonisch: I'm
afraid you'll have to make this decision on your own. If you find a problem with your driver
selection, try the other provider – it might fix the problem.
Dazu ist zu ergänzen: Sie sollten sich möglichst früh für einen Treiber entscheiden.
Wenn Sie den Treiber später austauschen, müssen Sie Ihren gesamten Code vollständig neu durchtesten (und in einigen Details sicherlich ändern)! Wenn Sie also kurz vor
Ende Ihres Projekts auf ein Problem stoßen, stehen Sie vor einem Dilemma.
Die Beispiele in diesem Buch nutzen fast durchweg den SQLOLEDB-Treiber (wenn
nicht, wird darauf hingewiesen). Dieser Treiber gilt trotz diverser Kinderkrankheiten
als effizienter und wird sich wohl längerfristig durchsetzen.
VERWEIS
Verbindung zu Oracle-Datenbanken
Mangels Testmöglichkeiten gibt dieses Kapitel keine konkreten Informationen
zum Verbindungsaufbau zu Oracle-Datenbanken. Einen Überblick über die
Schlüsselwörter gibt der KB-Artikel Q179236 in der MSDN-Library. (Weitere
Oracle-spezifische KB-Artikel finden Sie übrigens, wenn Sie nach dem Schlüsselwort kbOracle suchen.)
6.3.3
Einstellungen für den
SQL-Server-Treiber SQLOLEDB
Elementare ConnectionString-Parameter
Data Source=rechnername: Data Source gibt an, auf welchem Rechner der SQL Server
läuft. Es muß also ein Rechnername angegeben werden. Für die Beispiele dieses Buchs
ist das immer mars. Falls der SQL Server am selben Rechner wie der Client läuft, kann
auch einfach localhost verwendet werden.
Initial Catalog=datenbankname: Initial Catalog gibt an, zu welcher vom SQL Server
verwalteten Datenbank die Verbindung hergestellt wird (also z.B. zu pubs oder zu
northwind).
196
6 Connection-Objekt (Verbindungsaufbau)
Integrated Security=SSPI: Diese Einstellung bedeutet, daß der Datenbank-Login auf
Basis der Windows-NT-Sicherheitsmechanismen erfolgen soll. Die Alternative zu Integrated Security ist die explizite Angabe eines Login-Namens und eines Paßworts (siehe
die beiden folgenden Schlüsselwörter). SSPI steht übrigens für Security Support Provider Interface, einer Sammlung von Funktionen zur Authentifizierung unter Windows
NT.
User ID=name: Gibt den SQL-Server-User-Namen an (nur erforderlich, wenn nicht
mit integrierter Sicherheit gearbeitet wird).
HINWEIS
Password=paßwort: Gibt das Paßwort zum User-Namen an (nur erforderlich, wenn
nicht mit integrierter Sicherheit gearbeitet wird).
Integrated Security funktioniert nur, wenn der SQL Server unter NT läuft, nicht
aber, wenn eine MSDE unter Windows 9x ausgeführt wird. In diesem Fall müssen Sie mit User ID und Password arbeiten.
In den meisten Fällen ist die folgende Zeichenkette bereits ausreichend, um eine Verbindung zur pubs-Datenbank auf Rechner mars herzustellen. Für alle weiteren Parameter (siehe unten) werden die Defaultwerte verwendet, die zumeist vernünftig sind.
conn.ConnectionString = "Provider=SQLOLEDB;Data Source=mars;" & _
"Initial Catalog=pubs;Integrated Security=SSPI"
Prompt-Parameter (Login-Dialogbox)
Prompt=prompt / noprompt / complete / completerequired: Dieser Parameter bestimmt, ob und wie ein Dialog zur manuellen Eingabe der Verbindungsinformationen
angezeigt werden soll. noprompt bedeutet, daß der Dialog selbst dann nicht angezeigt
wird, wenn der Verbindungsaufbau nicht gelingt. Statt dessen kommt es zu einer
Fehlermeldung. Gerade umgekehrt funktioniert prompt: der Dialog wird in jedem Fall
vor dem ersten Verbindungsversuch angezeigt.
HINWEIS
Bei complete und completerequired wird der Dialog nur dann angezeigt, wenn der Verbindungsaufbau nicht klappt, die Verbindungsdaten also vervollständigt oder korrigiert werden müssen. Der Unterschied zwischen den beiden Varianten: Bei completerequired ist der untere Teil des Dialogs gesperrt, d.h., es können nur die Basisinformationen angegeben werden (Server, Name, Paßwort).
Die Defaulteinstellung des Treibers lautet an sich noprompt. Wenn Sie allerdings
mit dem DataEnvironment-Designer arbeiten, gibt diese Komponente eine andere Defaulteinstellung vor, nämlich complete. Wenn Sie trotz Designer eine andere Einstellung wünschen, müssen Sie Prompt im Eigenschaftsfenster (nicht im
DATALINK-Dialog!) ändern.
6.3 ConnectionString-Parameter
197
Bild 6.4: Der Dialog zur Einstellung der
Verbindungsinformationen
Darüber, was die beste Einstellung für Prompt ist, läßt sich trefflich streiten. Verwenden Sie noprompt, tritt ein Fehler auf, wenn die Datenbankverbindung nicht gelingt. Es
liegt dann in Ihrer Verantwortung, ob und wie der Anwender einen neuerlichen Verbindungsversuch durchführen kann. (Es kann z.B. sein, daß nach einer Reorganisation
des Netzwerks der Datenbank-Server einen neuen Server-Namen hat. Der Anwender
muß eine Chance haben, den Client auch unter derart veränderten Rahmenbedingungen zu starten.)
Wenn Sie complete[required] verwenden, sparen Sie sich derartigen Code. Wenn es jetzt
immer noch zu einem Fehler kommt, hat der Anwender ABBRUCH angeklickt, das Programm muß also nur noch beendet werden. Der Nachteil: Sie handeln sich ein Sicherheitsrisiko ein. Der Anwender kann beliebig oft versuchen, einen Login-Namen und
ein Paßwort zu erraten.
Weitere Parameter in alphabetischer Reihenfolge
Die obigen Einstellungen sind im Regelfall ausreichend. Die folgenden Parameter sind
optional und ermöglichen eine Feinsteuerung der Verbindungsoptionen.
Application Name=name: Gibt an, unter welchem Anwendungsname sich das Programm beim SQL Server melden soll. Normalerweise wird der in der VB-Entwicklungsumgebung eingestellte Name (PROJEKT|EIGENSCHAFTEN|ERSTELLEN) verwendet.
Auto Translate=true / false: Gibt an, ob eine automatische Konversion zwischen Unicode- und ANSI-Zeichenketten erfolgen soll (soweit notwendig). Die Defaulteinstellung ist true.
Connect Timeout=n: Gibt an, nach wie vielen Sekunden der Verbindungsversuch
abgebrochen wird. Der Defaultwert ist nicht dokumentiert, scheint aber 15 Sekunden
198
6 Connection-Objekt (Verbindungsaufbau)
TIP
zu betragen. Da das Programm während des Verbindungsversuchs vollständig blokkiert ist, sollte der Wert nicht zu hoch angesetzt werden und statt dessen ein Wiederholungsversuch gewährt werden. (Siehe auch Seite 221 zum Thema asynchroner Verbindungsaufbau!)
Eine mögliche Fehlerursache, wenn der Verbindungsaufbau nicht klappt, besteht darin, daß der SQL Server nicht gestartet ist. Besonders wahrscheinlich ist
das, wenn Sie mit der MSDE unter Windows 9x arbeiten. Dort ist es nämlich
unmöglich, die MSDE automatisch beim Hochfahren des Rechners als Dienst
zu starten, wie dies unter NT üblich ist. Weisen Sie daher in der Fehlermeldung
darauf hin, gegebenenfalls die MSDE zu starten!
Current Language=english / german ...: Gibt an, in welcher Sprache der SQL Server
Fehlermeldungen formulieren soll. Wenn keine Sprache angegeben wird, gilt die Defaulteinstellung des SQL Servers. Es dürfen nur Sprachen verwendet werden, die vom
SQL Server unterstützt werden (und die auch installiert sind!).
Die Einstellung hat übrigens keinen Einfluß auf die Sprache von ADO-Fehlermeldungen. Selbst wenn Sie Current Language=english verwenden, werden Sie oft mit glücklos
übersetzten ADO-Fehlermeldungen konfrontiert und können nur raten, was ursprünglich wohl gemeint war.
Locale Identifier=n: Gibt die ID-Nummer der gewünschten Ländereinstellung an.
Leider ist nicht dokumentiert, worauf diese Einstellung eigentlich Einfluß nimmt.
(Sicherlich nicht auf die Fehlermeldungen – siehe oben. Aber auch nicht auf das Datumsformat in SQL-Kommandos und in deren Ergebnissen. Unter Umständen bestimmt die ID-Nummer, wie Sonderzeichen in ANSI-Zeichenketten interpretiert oder
in Unicode übersetzt werden – aber auch das konnte durch Experimentieren nicht
verifiziert werden.)
Wichtige ID-Nummern sind auf jeden Fall 1031 (deutsch) und 1033 (englisch). Eine
Liste der wichtigsten ID-Nummern finden Sie in der TSQL-Referenz, Schlüsselwort
syslanguages. Wie Sie die ID-Nummer der aktuellen Ländereinstellungen mit VB-Code
ermitteln können, verrät Knowledge Base Artikel Q199244. Wenn keine ID-Nummer
angegeben wird, wird offensichtlich die aktuelle Ländereinstellung des Clients verwendet.
Persist Security Info=true / false: Der Parameter bestimmt, ob das Paßwort nach dem
Verbindungsaufbau noch in ConnectionString gespeichert werden soll. Der Parameter
ist daher nur von Interesse, wenn nicht auf die NT-Authentifizierung zurückgegriffen
wird.
Wenn der Parameter auf false gestellt wird, wird das Paßwort nach dem Verbindungsaufbau aus ConnectionString entfernt. Das erschwert es, das Paßwort auszuspionieren.
Aus unerfindlichen Gründen lautet die Defaulteinstellung true, d.h., es ist ratsam,
6.3 ConnectionString-Parameter
199
diesen Parameter explizit auf false zu setzen. (Der DataEnvironment-Designer macht
dies auf jeden Fall.)
HINWEIS
Use Procedure for Prepare=0/1/2: Gibt an, ob ADO-Command-Objekte am SQL Server
als temporäre Prozeduren nachgebildet werden sollen, um sie in der Folge effizienter
auszuführen. 0 bedeutet No (keine Prozeduren), 1 und 2 bedeuten Yes. Der Unterschied besteht darin, daß die Prozedur bei 2 länger in der Datenbank gespeichert
bleibt, um ein eventuelles Neuübersetzen bei einer abermaligen Verwendung zu vermeiden. Die Defaulteinstellung ist 1.
Vermutlich gilt Use Procedure for Prepare nur für den SQL Server 6.5. Seit Version 7 hat der SQL Server einen neuen Mechanismus zur Ausführung von Command-Objekten mit Prepared=True. Der wesentliche Vorteil: es ist jetzt nicht
mehr erforderlich, daß Prozeduren erzeugt und in der tempdb-Datenbank gespeichert werden.
Die Dokumentation ist in diesem Punkt leider nicht wirklich schlüssig. Experimente mit dem SQL Profiler haben auf jeden Fall ergeben, daß die Ausführung
von kompilierten Abfragen unabhängig von der Einstellung dieses Parameters
immer gleich ablaufen (siehe auch Seite 287).
VORSICHT
Wenn Ihnen bei den Schlüsselwörtern in ConnectionString ein Tippfehler passiert, wird die Einstellung einfach ignoriert. Es kommt zu keiner Fehlermeldung, weswegen das Problem leicht übersehen werden kann.
ANMERKUNG
Workstation ID=clientname: Gibt den Rechnernamen an, auf dem der Client ausgeführt wird. Wenn kein Wert angegeben wird, wird der lokale Rechnername verwendet
(was ohnedies der einzig sinnvolle Wert erscheint). Aus der Dokumentation geht nicht
hervor, welche Aufgabe diese Einstellung erfüllt. Der Client wird selbst dann bedient,
wenn ganz offensichtlich ein falscher Name (oder auch eine Zahl) angegeben wird.
Wenn Sie den Dialog des DataEnvironment-Designers bzw. des Adodc-Steuerelements verwenden, um ConnectionString einzustellen, enthält die Zeichenkette
jede Menge Einstellungen, die zumindest überflüssig sind (weil es sich ohnedies um Defaulteinstellungen handelt). Irritierend ist insbesondere die Einstellung für Workstation ID. Wenn das Programm später auf einem anderen Rechner ausgeführt wird (der Normalfall!), wird eine falsche Rechner-ID vorgespiegelt. Selbst wenn das der Verbindung zum SQL Server offensichtlich nicht
schadet, stellt sich die Frage, wozu diese Einstellung dient.
200
6.3.4
6 Connection-Objekt (Verbindungsaufbau)
Einstellungen für den ODBC-Treiber MSDASQL
Der ODBC-Treiber für OLE DB stellt sozusagen die Brücke zwischen der alten und der
neuen Datenbankstrategie von Microsoft dar. Da nicht zu erwarten war, daß alle Datenbankhersteller sofort OLE-DB-Treiber für ihre Datenbanken zur Verfügung stellen
können, kann OLE DB und damit ADO auch über den Umweg über ODBC genutzt
werden.
Wie am Eingang dieses Abschnitts bereits erwähnt, ist MSDASQL selbst dann eine
interessante Alternative, wenn bereits OLE-DB-Treiber existieren – nämlich dann,
wenn es mit den neuen Treiber noch Probleme gibt. Peinlicherweise gilt das in bestimmten Situationen selbst für den OLE-DB-Treiber zum SQL Server, obwohl dieser aus
erster Hand kommt und eigentlich ein Musterbeispiel für funktionierende OLE-DBTechnologie sein sollte.
Wer bereits von früheren VB-Versionen Erfahrung mit ODBC-Datenquellen hat, für
den gibt es trotz des zwischengeschaltenen MSDASQL-Treiber wenig Neuigkeiten: die
Syntax für ConnectionString ist praktisch unverändert geblieben. Die Kompatibilität
geht so weit, daß ConnectionString-Zeichenketten ohne Provider-Angabe automatisch
den MSDASQL-Treiber verwenden. Leider bedeutet diese Kompatibilität auch, daß
sich die meisten MSDASQL-Schlüsselwörtern von ihren SQLOLEDB-Äquivalenten
unterscheiden.
ODBC-Datenquellen
Grundvoraussetzung für das Verständnis von ODBC-Datenquellen sind sogenannte
Data Source Names (DSN). Dabei handelt es sich um Verbindungsinformationen zu
Datenbanken, die zentral im ODBC-Datenquellen-Administrator angelegt werden
können. (Dieses Programm ist über die Systemsteuerung zugänglich.)
Es gibt drei Typen von DSNs: System-, Benutzer- und Datei-DSNs. System- und Benutzer-DSNs werden in der Registrierdatenbank gespeichert und stehen allen Benutzern bzw. nur einem speziellen Benutzer zur Verfügung. Datei-DSNs entsprechen
weitgehend den *.udl-Dateien (siehe Seite 226).
Ist eine DSN einmal definiert, kann jedes ODBC-Programm darauf einfach durch
Nennung des (Datei-)Namens zugreifen. Darüber hinaus muß nur noch der Benutzername und das Paßwort angegeben werden, wenn nicht auf die NT-Authentifizierung
zurückgegriffen wird.
Im Prinzip entspricht die Definition einer ODBC-DSN der Definition einer neuen Datenverknüpfung im Datenansichtfenster bzw. dem Anlegen einer *.udl-Datei. Das
Verwaltungsprogramm für ODBC-Datenquellen bietet allerdings einige zusätzliche
Einstellungsmöglichkeiten, die sowohl bei der effizienten Verwaltung von Verbindungen (pooling) als auch bei der Fehlersuche (Ablaufverfolgung) helfen.
6.3 ConnectionString-Parameter
201
Bild 6.5: Verwaltung von ODBC-Datenquellen
Elementare ConnectionString-Parameter
Wenn eine DSN definiert ist, reduzieren sich die ConnectionString-Einstellungen zumeist auf die folgenden Parameter. (Wenn die DSN auf NT-Authentifizierung zurückgreift oder wenn Name und Paßwort in einen Login-Dialog angegeben werden sollen,
ist die DSN-Angabe allein schon ausreichend!)
DSN=datenquellenname: Gibt den Namen der Datenquelle an.
Trusted_Connection=yes: Gibt an, daß die NT-Authentifizierung verwendet werden
soll. (Dieser Parameter verschwindet nach dem Verbindungsaufbau aus dem ConnectionString. Ob eine existierende Verbindung NT-Authentifizierung verwendet oder
nicht, ist daran zu erkennen, ob das Schlüsselwort UID in ConnectionString enthalten
ist oder nicht.)
UID=name: Gibt den Benutzernamen an (nur, wenn nicht die NT-Authentifizierung
verwendet wird).
PWD=paßwort: Gibt das Paßwort an.
Eine ODBC-Verbindung kann aber auch dann hergestellt werden, wenn keine DSN
existiert. Es müssen dann aber natürlich alle Verbindungsinformationen angegeben
werden.
202
6 Connection-Objekt (Verbindungsaufbau)
Driver=treibername: Gibt an, welcher ODBC-Treiber verwendet werden soll. Für Verbindungen zum SQL Server muß SQL Server (mit Leerzeichen, ohne Bindestrich) verwendet werden.
Server=rechnername: Gibt an, auf welchem Rechner der Datenbank-Server ausgeführt
wird.
Database=datenbankname: Gibt den Namen der Datenbank an.
Das folgendene ConnectionString-Beispiel ermöglicht den Verbindungsaufbau zu pubs.
Der SQL Server läuft am Rechner mars. Da weder ein Benutzername noch ein Paßwort
angegeben wird, wird die NT-Authentifizierung verwendet.
TIP
conn.ConnectionString = _
"Provider=MSDASQL.1;Driver=SQL Server;Server=mars;Database=pubs"
Der MSDASQL-Treiber scheint Probleme mit der Auswertung des Prompt-Parameters zu haben (der an sich existiert). Statt das gewünschte Verhalten in der
ConnectionString-Zeichenkette anzugeben, müssen Sie einen Umweg einschlagen (siehe KB-Artikel Q193128). Führen Sie vor der Open-Methode die folgende
Anweisung aus:
conn.Properties("Prompt") = adPromptAlways / adPromptNever ...
Die adPromptXxx-Konstanten sind in der ADODB-Bibliothek definiert.
Data-Link-Datei nutzen
File Name=udl-Dateiname: Statt all der obigen Parameter können Sie auch nur einen
einzigen verwenden, nämlich File Name. Als Wert geben Sie den Pfad einer *.udlDatei an, die dann alle weiteren Verbindungsinformationen enthält (siehe Seite 226).
TIP
Weitere Parameter in alphabetischer Reihenfolge
Die folgende Zusammenstellung verweist auf gleichwertige oder vergleichbare
Parameter im OLE-DB-ConnectionString – siehe Seite 197.
APP=programmname: Gibt den Namen des Anwendungsprogramms an (entspricht
Application Name bei OLE-DB-Datenquellen).
Extended Properties="p1=w1;p2=w2...": Einige ODBC-spezifische Parameter werden
spätestens beim Verbindungsaufbau – also beim Ausführen von conn.Open – als Extended Properties neu gruppiert. (Sie müssen vor dem Verbindungsaufbau aber durchaus nicht als Extended Properties angegeben werden!) Ein typischer ODBC-ConnectionString nach erfolgreichem Verbindungsaufbau sieht folgendermaßen aus:
6.3 ConnectionString-Parameter
203
Provider=MSDASQL.1;Locale Identifier=1031;
Extended Properties="DRIVER=SQL Server;SERVER=MARS;
APP=Visual Basic;WSID=URANUS;DATABASE=pubs"
Das Problem: Das Auslesen einzelner Parameter mit conn.Properties("paramname")
funktioniert nicht mehr. Die Extended Properties können nur als Block ausgelesen werden.
Language=english / german ...: Bestimmt die Sprache von Fehlermeldungen (entspricht Current Language).
Locale Identifier=n: Wie bei OLE DB.
LoginTimeout=n: Gibt die maximale Login-Zeit in Sekunden an (entspricht Connect
Timeout).
Mode= read / write / readwrite / share deny write ...: Gibt an, wie der Datenbankzugriff erfolgen soll. Im DataEnvironment-Designer kann diese Einstellung (je nach Treiber) im Dialogblatt WEITERE durchgeführt werden.
Persist Security Info=true / false: Wie bei OLE DB.
QueryLogFile=a und TranslationName=b: Hier haben die Treiberprogrammierer bei
Microsoft einiges durcheinandergebracht. Nach dem Verbindungsaufbau ist QueryLogFile=Yes, falls Sie NT-Authentifizierung verwenden. TranslationName bekommt den
Wert a (also das, was Sie für QueryLogFile einstellen wollten). Nun ja ...
Welche Bedeutung die Parameter wirklich haben und ob die vom Treiber fehlerhaft
durchgeführten Zuweisungen irgendwelche Konsequenzen hat, ist unklar geblieben.
(Nähere Angaben zu TranslationName finden Sie bei SQLConfigDataSource in der
ODBC-API-Referenz des SQL Servers.)
WSID=clientname: Gibt den Rechnernamen des Client an (entspricht Workstation ID).
6.3.5
Einstellungen für den Jet-Treiber
Eigentlich beschränkt sich dieses Buch ja auf richtige Datenbank-Server und macht um
die Jet-Engine einen Bogen. Nur der Vollständigkeit halber gibt dieser Abschnitt eine
ganz knappe Beschreibung des Verbindungsaufbaus zu Jet-Datenbanken.
Im Regelfall reicht die Einstellung von nur zwei Schlüsselwörtern aus: Provider und
Data Source. Die Provider-Einstellung hängt davon ab, welche Jet-Version Sie verwenden: Microsoft.Jet.OLEDB.4.0 oder Microsoft.Jet.OLEDB.3.51. An Data Source muß einfach der vollständige Name der *.mdb-Datei übergeben werden. Wenn sich die Datei
im gleichen Verzeichnis wie die *.exe-Datei befindet, führt folgender Code zum Ziel:
conn.ConnectionString = "Provider=Microsoft.Jet.OLEDB.3.51;" & _
"Data Source=" & App.Path & "\nwind.mdb"
204
6 Connection-Objekt (Verbindungsaufbau)
Wenn Sie die Zeichenkette mit dem DataEnvironment-Designer einstellen, ergibt sich
dagegen das folgende Ungetüm für ConnectionString:
Provider=Microsoft.Jet.OLEDB.4.0;
Data Source=J:\bin-srv\vs98\VB98\Nwind.mdb;
User ID=Admin;Password="";
Mode=Share Deny None;
Locale Identifier=1033;
Jet OLEDB:System database="";
Jet OLEDB:Registry Path="";
Jet OLEDB:Database Password="";
Jet OLEDB:Engine Type=4;
Jet OLEDB:Database Locking Mode=0;
Jet OLEDB:Global Partial Bulk Ops=2;
Jet OLEDB:Global Bulk Transactions=1;
Jet OLEDB:New Database Password="";
Jet OLEDB:Create System Database=False;
Jet OLEDB:Encrypt Database=False;
Jet OLEDB:Don't Copy Locale on Compact=False;
Jet OLEDB:Compact Without Replica Repair=False;
Jet OLEDB:SFP=False
6.4
Eigenschaften des Connection-Objekts
HINWEIS
Der vorangegangene Abschnitt gab eine umfassende Beschreibung von ConnectionString, der wichtigsten Eigenschaft des Connection-Objekt. Aber auch wenn diese Zeichenkette viele Verbindungsmerkmale steuert, so gibt es dennoch einige weitere Parameter, die durch separate Eigenschaften gesteuert werden. Dieser Abschnitt gibt
darüber einen ersten Überblick.
Ein Teil der hier beschriebenen Eigenschaften steht auch beim Adodc-Steuerelement zur Verfügung. Etwas unübersichtlicher wird es, wenn Sie mit dem DataEnvironment-Designer arbeiten: Sie müssen jetzt zwischen zwei Objekten unterscheiden. Während der Programmentwicklung haben Sie es mit DEConnectionObjekten zu tun, deren Eigenschaften Sie im DATALINK-Dialog bzw. im Eigenschaftsfenster einstellen. Im laufenden Programm stellt der Designer dann aber
ganz normale ADO-Connection-Objekte zur Verfügung. Die DEConnectionEigenschaften werden ab Seite 206 beschrieben.
HINWEIS
6.4 Eigenschaften des Connection-Objekts
205
Manche Parameter, etwa das Zeitlimit für den Verbindungsaufbau, können sowohl im ConnectionString (z.B. "Connect Timeout=n") als auch durch Eigenschaften eingestellt werden (z.B. conn.ConnectionTimeout=n). In solchen Fällen scheinen Eigenschaften Vorrang gegenüber der ConnectionString-Zeichenkette zu
genießen. Nach dem Verbindungsaufbau wird ConnectionString dann an die
tatsächlich benutzten Verbindungsmerkmale angepaßt.
6.4.1
Connection- und Adodc-Eigenschaften
CommandTimeout: So wie ConnectionTimeout ein Zeitlimit für den Verbindungsaufbau angibt, bestimmt diese Eigenschaft, wieviel Zeit bei der Ausführung eines SQLKommandos bzw. einer Abfrage verstreichen darf. Der Defaultwert von 30 Sekunden
ist bisweilen zu niedrig.
ConnectionTimeout: Entspricht "Connection Timeout" im ConnectionString, gibt also
an, wie lange der Verbindungsaufbau maximal dauern darf.
CursorLocation: Gibt an, ob im weiteren geöffnete Recordsets mit Client- oder Serverseitigen Cursorn arbeiten sollen. Als Daumenregel gilt: Client-seitige Cursor sind vielseitiger, manche ADO-Funktionen funktionieren nur damit (z.B. hierarchische Recordsets). Allerdings brauchen Client-seitige Recordsets mehr Ressourcen am ClientRechner und sind für sehr große Datensatzlisten schlecht geeignet (siehe auch Seite
256).
IsolationLevel: Gibt an, wie stark unterschiedliche Transaktionen voneinander abgeschirmt werden. Die Defaulteinstellung (adXactCursorStability, gleichwertig mit adXactReadCommited) bedeutet, daß durch eine fremde Transaktion durchgeführte Änderungen für die laufende Transaktion erst sichtbar werden, wenn die fremde Transaktion abgeschlossen ist. Nicht alle möglichen Einstellungen werden von allen Datenbank-Servern bzw. OLE-DB-Treibern unterstützt. Weitere Informationen finden Sie in
der MSDN-Library (suchen Sie in der SQL-Server-Dokumentation nach "Isolation
Level")!
Mode: Entspricht "Mode" im ConnectionString, gibt also an, wie der Zugriff auf die
Datenbank erfolgt (z.B. read only). Die Defaulteinstellung lautet adModeUnknown und
überläßt die Frage dem Gutdünken des OLE-DB-Treibers oder der Datenbank.
Provider: Entspricht "Provider" im ConnectionString, gibt also an, welcher OLE-DBTreiber verwendet wird.
State: Gibt den Zustand der Verbindung an (read only). Mögliche Zustände sind adStateClosed, -Opened, -Connecting, -Executing und -Fetching.
Version: Gibt die ADO-Versionsnummer an (zur Zeit 2.1).
206
6 Connection-Objekt (Verbindungsaufbau)
6.4.2
DEConnection-Eigenschaften
Die DEConnection-Eigenschaften stehen unter den hier angegebenen Namen nur während des Programmentwurfs in der VB-Entwicklungsumgebung zur Verfügung. Die
Eigenschaften können daher nicht im Programmcode genutzt werden! Sie können nur
manuell in der Entwicklungsumgebung eingestellt werden. Sie bestimmen die Merkmale des Connection-Objekts, das der Designer später dem laufenden Programm zur
Verfügung stellt.
CommandTimeout: Siehe oben.
ConnectionSource: Enthält die Zeichenkette, die beim Verbindungsaufbau als ConnectionString verwendet wird.
ConnectionTimeout: Siehe oben.
CursorLocation: Siehe oben.
DesignPromptBehavior: Entspricht "Prompt=.." in ConnectionString, gibt also an, ob
bei Verbingungsproblemen während der Arbeit in der VB-Entwicklungsumgebung
der SQL-Login-Dialog angezeigt werden soll.
DesignPassword: Dieses Paßwort wird während der Programmentwicklung verwendet, um eine Verbindung zur Datenbank herzustellen. Vorsicht: Das Paßwort wird im
Klartext (nicht durch Sternchen) im Eigenschaftsfenster angezeigt.
DesignUserName: Gibt an, welcher Benutzername während der Programmentwicklung verwendet werden soll.
HINWEIS
DesignSaveAuthentification: Gibt an, ob das DesignPassword gespeichert werden soll.
Wenn Sie hier True angeben, wird das Paßwort in der *.dsr-Datei unverschlüsselt
gespeichert.
Die Verwendung der DesignPassword-, DesignUserName und DesignSaveAuthentification-Eigenschaften ist nur dann notwendig, wenn Sie aus irgendeinem
Grund nicht mit NT-Authentifizierung arbeiten können. Durch die Angabe dieser Eigenschaften vermeiden Sie, daß Sie bei jedem Zugriff des Designers auf
die Datenbank nach dem Paßwort gefragt werden.
RunPromptBehavior, RunPassword, RunUserName und RunSaveAuthentification:
Diese Eigenschaften entsprechen den vier vorangegangenen, gelten aber jetzt für die
Ausführung bzw. Kompilierung des Programms. Die Trennung von Design- und RunEigenschaften erlaubt ein komfortables Arbeiten in der Entwicklungsumgebung, ohne
das resultierende Programm mit zu großen Zugriffsrechten auszustatten (und so ein
Sicherheitsrisiko einzugehen).
6.5 Fehlerabsicherung
6.4.3
207
Zusätzliche Eigenschaften (Properties)
Die Aufzählung Properties wurde bereits als eine komfortable Möglichkeit erwähnt,
einzelne Parameter der ConnectionString-Zeichenkette zu lesen bzw. zu verändern.
Nach Herstellung einer Verbindung enthält diese Eigenschaft aber noch viel detailliertere Informationen, die nicht in ConnectionString enthalten sind.
Die Properties-Aufzählung verweist auf lauter Property-Objekte. Diese enthalten in
Name den Namen der Eigenschaft, in Type den Datentyp (z.B. adBoolean für Wahrheitswerte) und in Value den Inhalt.
Wenn Sie via OLE-DB-Treiber eine Verbindung zum SQL Server herstellen, enthält die
Properties-Aufzählung gezählte 80 Einträge (siehe Bild 6.6). So toll es ist, daß Informationen über wirklich fast jedes Detail der Verbindung zugänglich sind, so trostlos ist
es, daß eine Dokumentation dieser Einträge fehlt.
Bild 6.6: Eigenschaften einer Verbindung
zu einer SQL-Server-Datenbank
' Connection\Properties\Properties.vbp
Private Sub cmdProperties_Click()
Dim prop As Property
Text1.Text = ""
For Each prop In conn.Properties
Text1.Text = Text1.Text & prop.Name & ": " & prop.Value & vbCrLf
Next
End Sub
6.5
Fehlerabsicherung
Wenn ein Datenbank-Client startet, besteht seine erste Aufgabe fast immer darin, die
Verbindung zur Datenbank herzustellen. Falls der Datenbank-Server oder das Netzwerk stark belastet sind, kann das einige Zeit kosten. Außerdem gibt es unzählige
208
6 Connection-Objekt (Verbindungsaufbau)
Gründe, weswegen der Verbindungsaufbau überhaupt scheitern kann: Netzwerkprobleme, Authentifizierungsprobleme, Lizenzprobleme etc. (Oft ist die Anzahl der
gleichzeitigen Datenbankverbindungen durch eine Lizenz limitiert; wenn diese Anzahl erschöpft ist, werden weitere Verbindungsversuche blockiert.)
Ihr Programm sollte sich in dieser Verbindungsphase möglichst benutzerfreundlich
verhalten.
• Es sollte vor Beginn des Verbindungsaufbaus darauf hinweisen, daß der Vorgang
einige Sekunden beanspruchen kann und daß das Programm während dieser Zeit
blockiert ist. (Noch eleganter ist ein asynchroner Verbindungsaufbau – siehe Seite
221.)
• Wenn der Verbindungsaufbau scheitert, sollte das Programm einen weiteren Versuch ermöglichen. Der Anwender sollte dabei die Chance haben, Verbindungsdaten zu korrigieren (Server-Name, User-Name, Paßwort).
• Falls der Anwender Verbindungsdaten ändert, sollten diese (bis auf das Paßwort!)
gespeichert werden und bei einem neuerlichen Programmstart wieder verwendet
werden.
• Ideal wäre, wenn das Programm Informationen darüber anzeigen könnte, warum
es nicht geklappt hat. Leider scheitert dieser Versuch oft daran, daß Ihr Programm
keine hellseherischen Fähigkeiten hat und die ADO-Fehlermeldungen nicht immer
weiterhelfen. Versuchen Sie dennoch, eine möglichst plausible Erklärung zu geben
und auf typische Fehlerursachen (etwa eine nicht gestartete MSDE) hinzuweisen.
VORSICHT
Im weiteren Verlauf dieses Kapitels finden Sie Beispiele für alle drei Verbindungsvarianten (ADO-Code, Adodc, DataEnvironment).
Beachten Sie bitte, daß das Verhalten des Programms stark vom gewählten
Treiber abhängt (Fehlermeldungen, Fehlernummern, Parameternamen in ConnectionString etc.). Mit Ausnahme von Abschnitt 6.5.2, der explizit auf MSDASQL eingeht, gelten alle Beispiele nur für den SQLOLEDB-Treiber!
6.5.1
ADO-Code (SQLOLEDB)
Die folgende Variante des Einführungsbeispiels von 186 kommt den obigen Anforderungen schon einigermaßen nahe. Während des Verbindungsaufbaus wird ein Infotext in einem kleinen Formular (formConnectInfo) angezeigt. Wenn der Verbindungsaufbau nicht auf Anhieb klappt, wird der SQL-SERVER-LOGIN-Dialog angezeigt, in dem
die elementaren Verbindungseinstellungen verändert werden können. Wenn es mit
neuen Verbindungsdaten klappt, werden diese in der Registrierdatenbank gespeichert, damit dem Anwender beim nächsten Programmstart dieselben Fragen erspart
bleiben. (Das Paßwort wird natürlich nicht gespeichert.) Klappt der Verbindungsaufbau nicht, kann der Anwender das Programm durch ABBRUCH beenden.
6.5 Fehlerabsicherung
209
Bild 6.7: Das Programm bietet die Möglichkeit,
die Verbindungsinformationen zu vervollständigen
Der Code beginnt damit, daß das Authentifizierungsverfahren, der Server-Name und
gegebenenfalls der Anwendername mit GetSetting aus der Registrierdatenbank gelesen werden. Wenn noch keine derartigen Einträge existieren, werden als Defaultwerte
SSPI und mars verwendet. Anschließend wird das Infoformular angezeigt. (Refresh ist
notwendig, damit der Text tatsächlich sichtbar wird.)
' Connection\ADO-Code\Failsafe\Failsafe.vbp
Dim conn As Connection
Dim rec As Recordset
Private Sub Form_Load()
Dim dataSource$, intSecurity$, userID$
Dim result&
' Server-Name und Authentifikations-Infos aus Registry lesen
dataSource = GetSetting("mk", "sqlconnect", "DataSource", "MARS")
intSecurity = GetSetting("mk", "sqlconnect", "IntSecurity", "SSPI")
userID = GetSetting("mk", "sqlconnect", "userID", "")
' Info-Formular anzeigen
formConnectinfo.Show
formConnectinfo.Refresh
Aus den Daten der Registrierdatenbank wird nun der ConnectionString zusammengesetzt. Während des Verbindungsaufbaus gilt On Error Resume Next, um allfällige Fehler abzufangen.
' ConnectionString zusammensetzen
Set conn = New Connection
conn.ConnectionString = "Provider=SQLOLEDB;" & _
"Integrated Security=" & intSecurity & ";" & _
"Data Source=" & dataSource & ";" & _
"User ID=" & userID & ";" & _
"Initial Catalog=pubs;Prompt=completerequired;" & _
"Connect Timeout=15;Persist Security Info=false"
' Verbindung aufbauen
On Error Resume Next
210
6 Connection-Objekt (Verbindungsaufbau)
Do
Err = 0
conn.Open
If Err Then
result = MsgBox("Wollen Sie das Programm beenden?", vbYesNo)
If result = vbYes Then End
End If
Loop Until Err = 0
On Error GoTo 0
Nachdem der Verbindungsaufbau geklappt hat, werden die eventuell veränderten
Eckdaten wieder aus ConnectionString ausgelesen. Dabei muß darauf geachtet werden,
daß in Properties kein nichtvorhandener Parameter angegeben werden darf. Daher
wird dessen Existenz vorher mit InStr getestet. Um hier eventuelle Unstimmigkeiten
bezüglich der Groß- und Kleinschreibung auszuschließen, wird LCase eingesetzt.
(Properties berücksichtigt die Groß- und Kleinschreibung zum Glück nicht.) Die neuen
Werte werden mit SaveSetting in der Registrierdatenbank gespeichert.
' tatsächlich verwendete Verbindungsdaten in Registry speichern
dataSource = conn.Properties("Data Source")
If InStr(LCase(conn.ConnectionString), "integrated security") Then
intSecurity = conn.Properties("Integrated Security")
Else
intSecurity = ""
End If
If InStr(LCase(conn.ConnectionString), "user id") Then
userID = conn.Properties("User ID")
Else
userID = ""
End If
SaveSetting "mk", "sqlconnect", "DataSource", dataSource
SaveSetting "mk", "sqlconnect", "IntSecurity", intSecurity
SaveSetting "mk", "sqlconnect", "userID", userID
Damit kann die Verbindung nun genutzt werden, um wie im Einführungsbeispiel eine
einfache Abfrage durchzuführen und das Ergebnis im Textfeld anzuzeigen.
Unload formConnectinfo
' jetzt kann die Verbindung genutzt werden
Set rec = New Recordset
Set rec.ActiveConnection = conn
rec.Source = "SELECT pub_name FROM publishers ORDER BY pub_name"
rec.CursorType = adOpenKeyset
rec.Open
Set txtPubl.dataSource = rec
txtPubl.DataField = "pub_name"
End Sub
6.5 Fehlerabsicherung
211
Wenn Sie den Code weiter ausbauen möchten, hier einige Vorschläge:
• Der MsgBox-Text zum Programmende könnte informativer sein.
ANMERKUNG
• Die Verbindungseinstellungen könnten statt in der Registrierdatenbank auch
in einer lokalen Datei gespeichert werden. (Das ist eher eine Geschmacksfrage ohne erkennbare Vorteile.)
• Zur Eingabe von Server-Name, Login-Name und Paßwort sowie für die Option VERTRAUTE VERBINDUNG (also NT-Authentifizierung) könnte ein eigener
Dialog verwendet werden. Der Login-Dialog (also prompt=completerequired)
gilt als Sicherheitsrisiko, weil der Anwender unbegrenzt viele Versuche hat,
das Paßwort zu erraten. (Ob das Risiko durch einen eigenen Dialog viel geringer sein würde, sei dahingestellt. Aber zumindest könnte man den Einbruchsversuch unbequemer machen, protokollieren etc. Falls vorausgesetzt
werden kann, daß alle Clients NT-Authentifizierung nutzen können, kann
mit gutem Gewissen prompt=noprompt verwendet werden. Eine InputBox-Anweisung zur Eingabe eines eventuell geänderten Server-Namens wäre ausreichend.)
6.5.2
ADO-Code (MSDASQL/ODBC)
Wenn statt des SQLOLEDB-Treibers der MSDASQL-Treiber eingesetzt werden soll,
verändert sich an der Idee des Codes zwar nichts, wohl aber an der praktischen Ausführung. Zum einen haben die Parameter in ConnectionString jetzt andere Namen, zum
anderen ist die Auswertung nach erfolgtem Verbindungsaufbau komplizierter.
Der Parameter Trusted_Connection für die NT-Authentifizierung gilt nur für den Verbindungsaufbau. Danach ist das Schlüsselwort nicht mehr in ConnectionString enthalten ist. Ob tatsächlich NT-Authentifizierung verwendet wurde oder nicht, kann aus
der Existenz des UID-Schlüsselworts geschlossen werden.
Die Auswertung der ConnectionString-Zeichenkette wird noch dadurch erschwert, daß
die ODBC-relevanten Teile als Extended Properties zusammengefaßt sind. Daraus die
Einstellung eines Parameters zu extrahieren, ist zwar nicht schwierig, erfordert aber
eine Menge unübersichtlichen Code, der in eine Hilfsfunktion ausgelagert wurde.
Dazu ein Beispiel: ExtractValue("p1=w1;p2=w2", "p2") liefert als Ergebnis den Wert
von p2, also die Zeichenkette w2. Dabei werden alle möglichen Sonderfälle berücksichtigt. Wenn ein Parameter nicht gefunden wird, liefert die Funktion eine leere Zeichenkette zurück. Die Funktion kann nicht dazu verwendet werden, die Extended
Properties in ihrer Gesamtheit zu ermitteln.
212
6 Connection-Objekt (Verbindungsaufbau)
' Connection\ODBC\Failsafe\Failsafe.vbp
Dim conn As Connection
Dim rec As Recordset
Private Sub Form_Load()
Dim server$, uid$
Dim result&
server = GetSetting("mk", "odbcconnect", "Server", "MARS")
uid
= GetSetting("mk", "odbcconnect", "UID", "")
' Info-Formular anzeigen
formConnectinfo.Show
formConnectinfo.Refresh
' ConnectionString zusammensetzen
Set conn = New Connection
conn.ConnectionString = _
"Provider=MSDASQL;Driver=SQL Server;" & _
"Server=" & server & ";" & _
"Database=pubs;" & _
"Connect Timeout=5;Persist Security Info=false;"
If uid = "" Then
conn.ConnectionString = conn.ConnectionString + _
"Trusted_Connection=yes;"
Else
conn.ConnectionString = conn.ConnectionString + _
"UID=" & uid & ";"
End If
conn.ConnectionString = conn.ConnectionString + "QueryLogFile=abc;"
conn.Properties("Prompt") = adPromptAlways
' Verbindung aufbauen
... Code wie in Abschnitt 6.5.1
' tatsächlich verwendete Verbindungsdaten in Registry speichern
server = ExtractValue(conn.ConnectionString, "server")
uid = ExtractValue(conn.ConnectionString, "uid")
SaveSetting "mk", "odbcconnect", "Server", server
SaveSetting "mk", "odbcconnect", "UID", uid
' jetzt kann die Verbindung genutzt werden
... Code wie in Abschnitt 6.5.1
End Sub
Private Function ExtractValue$(all$, pattern$)
Dim pos1&, pos2&, pos2a&, pos2b&
' pos1 zeigt an den Beginn des Werts
pos1 = InStr(LCase(all), LCase(pattern) + "=")
If pos1 = 0 Then Exit Function
pos1 = pos1 + Len(pattern) + 1
6.5 Fehlerabsicherung
213
' pos2 zeigt an das Ende; das Ende wird durch die Zeichen ; oder "
' oder durch das Ende der Zeichenkette bestimmt
pos2a = InStr(pos1, all, """")
pos2b = InStr(pos1, all, ";")
If pos2a = 0 And pos2b = 0 Then 'weder ; noch " zu finden -->
pos2 = Len(all) + 1
'
bis Ende der Zeichenkette
ElseIf pos2a = 0 Then
'kein " daher gilt ;
pos2 = pos2b
ElseIf pos2b = 0 Then
'kein ; daher gilt "
pos2 = pos2a
Else
'sowohl als auch --> es gilt
pos2 = IIf(pos2a < pos2b, pos2a, pos2b) 'der kleinere Wert
End If
' Ergebnis
ExtractValue = Mid(all, pos1, pos2 - pos1)
End Function
6.5.3
Adodc (SQLOLEDB)
Wenn ein Programm mit dem Adodc-Steuerelement den Server nicht findet, zeigt es
die originelle Meldung [ADODC]; Unbekannter Fehler. [ADO] an. Nach der Bestätigung
dieser Meldung ist das Programm abermals für ein paar Sekunden blockiert, läuft
dann aber erstaunlicherweise weiter. Das Adodc-Feld wird deaktiviert, es werden keine Daten angezeigt. Alles in allem ist das Verhalten für den Anwender also in keiner
Weise akzeptabel.
Auf welche Weise können Sie nun auf das Startverhalten Einfluß nehmen? Gleich
vorweg: Code in Form_Load hilft nicht weiter. Form_Load wird nämlich erst nach der
Initialisierung des Adodc-Felds ausgeführt.
Login-Dialog bei Verbindungsproblemen anzeigen
HINWEIS
Die Lösung ist dennoch verblüffend einfach: Im Eigenschaftsfenster ergänzen Sie die
ConnectionString-Eigenschaft um Prompt=completerequired. Wenn es Verbindungsprobleme gibt, erscheint automatisch der nun schon bekannte SQL-Server-Login-Dialog
solange, bis die Verbindung klappt oder der Anwender ABBRECHEN drückt. (Dann
kommt es abermals zum bereits beschriebenen Fehler.)
Die Prompt-Einstellung ist im DATALINK-Dialog nicht möglich. Sie geht auch jedesmal verloren, wenn Sie diesen Dialog neu anzeigen. Beachten Sie bitte, daß
dieser Lösungsansatz bei ODBC-Datenquellen nicht funktioniert, weil der
MSDASQL-Treiber offensichtlich den Prompt-Parameter nicht richtig verarbeitet.
214
6 Connection-Objekt (Verbindungsaufbau)
Error-Ereignisprozedur
Der Adodc-Fehler kann in der Adodc_Error-Ereignisprozedur abgefangen werden. In
dieser Prozedur erhalten Sie allerdings keinerlei Informationen über die Natur des
Fehlers! (ErrorNumber enthält 0, Description die oben bereits zitierte Fehlermeldung.)
Um überhaupt festzustellen, ob es sich um ein Intialisierungsproblem handelt (oder
um einen anderen Fehler, der erst viel später im bereits laufenden Programm auftritt),
müssen Sie sich eines Tricks bedienen: Erstens testen Sie, ob Adodc.Recordset schon
initialisiert ist, und zweitens testen Sie, ob das Formular schon sichtbar ist. Wenn keine
dieser Bedingungen zutrifft, können Sie sich sicher sein, daß es ein Verbindungsproblem gibt.
Damit bleibt freilich noch offen, was Sie in der Prozedur jetzt tun. Sie können jetzt
zwar die ConnectionString-Eigenschaft verändern, aber wenn es auch beim zweiten
Versuch nicht klappt, tritt keine Fehlermeldung mehr auf. Statt dessen ist das AdodcFeld weiterhin nicht initialisiert, und das Programm läuft weiter, ohne Daten anzuzeigen.
HINWEIS
Der einzig erfolgsversprechende Ansatz bestünde darin, ein ADO-Connection-Objekt
zu erzeugen und darin probeweise eine Verbindung zur Datenbank aufzubauen. Dabei können alle in den beiden vorangegangenen Abschnitten beschriebene Techniken
eingesetzt werden. Wenn es geklappt hat, kopieren Sie die ConnectionStringEigenschaft des ADO-Objekts in das Adodc-Feld und beenden die Testverbindung
wieder.
Wenn Sie in der ConnectionString-Zeichenkette Prompt verwenden, dann gelangen Sie erst dann in Adodc_Error, wenn der Anwender den ABBRECHEN-Button
drückt. Im Regelfall sollte das Programm jetzt beendet werden – der Verbindungsaufbau ist definitiv gescheitert. Allein – Visual Basic ignoriert vorerst die
Anweisung End (und weigert sich auch, Unload Me auszuführen)!
Statt dessen versucht ADO nochmals, eine Verbindung herzustellen (gegebenenfalls wieder mit Anzeige des Login-Dialogs). Erst danach wird das EndKommando ausgeführt (übrigens auch dann, wenn der Verbindungsaufbau
nun doch klappen sollte). Wenn Sie also End ausführen möchten, sollten Sie
vorher ConnectionString auf "Connection Timeout=1" setzen, um den zweiten
Verbindungsversuch zumindest unmerkbar kurz zu halten.
Vielleicht fragen Sie sich mittlerweile, warum Sie eigentlich ein Adodc-Feld verwenden
(eigentlich wollten Sie sich damit Arbeit ersparen und nicht alles noch komplizierter
machen). Nun, Sie haben recht ... Und Sie verstehen jetzt auch, warum das Adodc-Feld
das nächste Mal erst im letzten Kapitel dieses Buchs (der ADO-Steuerelementereferenz) auftaucht, und auch dort nur kurz.
6.5 Fehlerabsicherung
215
Private Sub Adodc1_Error(ByVal ErrorNumber&, Description$, _
ByVal Scode&, ByVal Source$, ByVal HelpFile$, _
ByVal HelpContext&, fCancelDisplay As Boolean)
If (Adodc1.Recordset Is Nothing) And Me.Visible = False Then
' Initilisierungsfehler
Else
' normaler Fehler
End If
End Sub
Infofenster anzeigen
Wie bereits erwähnt, wird Form_Load erst ausgeführt, wenn die Datenverbindung
bereits aufrecht ist (oder gescheitert). Form_Load ist daher kein geeigneter Ort, um ein
Infofenster anzuzeigen. Es spricht aber nichts dagegen, den umgekehrten Weg zu
beschreiten: Sie starten die Programmausführung mit dem Infofenster und führen in
dessen Form_Load-Prozedur mainform.Show aus. Nachdem im Hauptformular Adodc
initialisiert wurde, schließen Sie das Infofenster wieder.
Verbindungsinformationen speichern
In den beiden vorangegangenen Beispielen wurde demonstriert, wie nach Herstellung
der Verbindung die Eckdaten in der Registrierdatenbank gespeichert und beim nächsten Start wieder verwendet wurden. Wenn Sie mit dem Adodc-Feld arbeiten möchten,
ist diese Vorgehensweise (fast) unmöglich. Sie können auf das Adodc-Steuerelement
nämlich nicht zugreifen, ohne daß dieses vorher initialisiert wird (und damit beißt sich
die Katze in den Schwanz). Natürlich gäbe es dennoch eine Möglichkeit: Sie speichern
in der ConnectionString-Eigenschaft bewußt eine ungültige Einstellung mit einem
möglichst kleinen Connection Timeout. Anschließend führen Sie die gewünschte Initialisierung in der Adodc_Error-Prozedur aus. (Auf eine Ausführung dieses Winkelzugs
wurde allerdings verzichtet.)
Beispielprogramm
Einmal abgesehen davon, daß sich das Programm keine Einstellungen merkt (siehe
oben), verhält es sich beinahe wie die Beispielprogramme der vorangegangenen Abschnitte. Die Programmiertechniken wurden ja schon beschrieben, jetzt fehlt nur noch
der Code. Dieser beginnt in Form_Load des Infofenster. Show und Refresh sind notwendig, damit dieses Fenster schon vor Ende von Form_Load sichtbar. Mit formMain.Show
beginnt die Initialisierung von Adodc im zweiten Fenster.
216
6 Connection-Objekt (Verbindungsaufbau)
' Connection\Adodc\Failsafe\Failsafe.vbp
' Connection\Adodc\Failsafe\formConnectinfo.frm
Private Sub Form_Load()
Show
Refresh
formMain.Show vbModeless
End Sub
Die ConnectionString-Eigenschaft von Adodc1 enthält also Prompt=completerequired.
Wenn es Verbindungsprobleme gibt, erscheint also der Login-Dialog. Wird hier ABBRUCH angeklickt, wird der Code in Adodc1_Error ausgeführt und das Programm hier
beendet. Wenn die Verbindung dagegen hergestellt werden kann, wird in Form_Load
das Infofenster geschlossen.
' Connection\Adodc\Failsafe\formMain.frm
Private Sub Adodc1_Error(ByVal ErrorNumber&, Description$, _
ByVal Scode&, ByVal Source$, ByVal HelpFile$, _
ByVal HelpContext&, fCancelDisplay As Boolean)
If (Adodc1.Recordset Is Nothing) And Me.Visible = False Then
' Verbindungsfehler, Programm so rasch wie möglich beenden
fCancelDisplay = True
Adodc1.ConnectionString = "Connection Timeout=1"
End
Else
' normaler Fehler
End If
End Sub
Private Sub Form_Load()
Unload formConnectinfo
End Sub
6.5.4
DataEnvironment (SQLOLEDB)
Das Defaultverhalten des DataEnvironment-Designers (für den Rest dieses Abschnitts
kurz DE-Designer) bei Verbindungsproblemen ist auch nicht gerade besonders gut
durchdacht: Es erscheint zwar der SQL-Server-Login-Dialog, dieser kann aber nicht
abgebrochen werden. (Wenn Sie ABBRUCH anklicken, läuft das Programm ohne Fehlermeldung weiter, im Formular werden aber keine Daten angezeigt.)
Fehler abfangen
Je nach Art der Nutzung des DE-Designers gibt es einen oder zwei Orte im Programm, an denen Sie Fehler beim Verbindungsaufbau abfangen können.
6.5 Fehlerabsicherung
217
• In jedem Fall wird bei einem Verbindungsfehler die ConnectionComplete-Ereignisprozedur mit dem Parameter Status=adStatusErrorsOccurred aufgerufen. (Sie müssen also Status auswerten, um festzustellen, ob die Verbindung errichtet wurde
oder nicht.)
• Wenn der erste Zugriff auf Objekte des DE-Designers per Code erfolgt (im Regelfall durch die Anweisung DE.connectionobject.Open), kann der Verbindungsfehler
auch hier mit den üblichen VB-Methoden erfolgen (z.B. On Error Resume Next mit
Auswertung von Err).
Wenn mit dem DE-Designer gearbeitet wird, sind im Startformular meist Steuerelemente mit DE-Objekten verbunden, d.h., es gilt die erste der beiden obigen Varianten.
In diesem Fall erfolgt die Initialisierung des DE-Designers noch vor Form_Load.
Die folgenden Zeilen zeigen ein Codefragment für die zweite Variante. Dabei wird die
Zuordnung zwischen DE-Objekten und Steuerelementen erst im Programmcode
durchgeführt, z.B. folgendermaßen:
' Form_Load des Startformulars mit Steuerelementen
Private Sub Form_Load()
' DE
.. DataEnvironment-Objekt
' rscomm .. ein Command-Objekt des DE-Designers
' txtbox .. ein Textfeld im Formular
On Error Resume Next
DE.rscomm.Open
If Err Then ... 'Fehlerbehandlung
Set txtbox.DataSource = DE.rscomm
txtbox.DataField = "feldname"
End Sub
HINWEIS
Diese Vorgehensweise ist allerdings ziemlich unbequem und widerspricht dem Konzept einer grafischen Entwicklungsumgebung. Eine Alternative besteht darin, die
Programmausführung nicht mit einem Datenformular, sondern mit einem anderen
Formular oder mit Main in einem Modul zu beginnen. Dann kann dort – mit Fehlerabsicherung – ein DE-Connection-Objekt geöffnet werden. Klappt das, wird im nächsten
Schritt das Datenformular mit den zur Datenquelle verbundenen Steuerelementen
angezeigt.
Damit während des Verbindungsaufbaus – wenn es dabei Probleme gibt – der
SQL-Login-Dialog automatisch erscheint, muß die Eigenschaft RunPromptBehaviour des Connection-Objekts des DE-Designers entsprechend eingestellt werden
(z.B. auf adPromptCompleteRequired). Diese Einstellung erfolgt im Eigenschaftsfenster.
218
6 Connection-Objekt (Verbindungsaufbau)
Ereignisprozeduren
Noch einige Worte zu den DE-Ereignisprozeduren, die im Verlauf der Initialisierung
ausgeführt werden:
• Als erstes wird DataEnvironment_Initialize ausgeführt. Diese Prozedur zeigt an, daß
die Initialisierung jetzt beginnt. (Bei der obigen ersten Variante ist das der erste VBCode, der überhaupt ausgeführt wird.)
• Anschließend wird für das betroffene Connection-Objekt die WillConnect-Ereignisprozedur ausgeführt. An diese Prozedur wird ein noch nicht geöffnetes ConnectionObjekt übergeben, dessen Eigenschaften verändert werden können (wenn auch mit
Einschränkungen, siehe unten). Erst nach Abschluß dieser Prozedur wird versucht,
die Verbindung tatsächlich herzustellen.
• Zu guter Letzt wird die ConnectComplete-Ereignisprozedur ausgeführt, um den
Abschluß des Verbindungsprozesses zu signalisieren. Beachten Sie bitte, daß diese
Prozedur nicht nur bei einer erfolgreichen Verbindung ausgeführt wird, sondern
auch, wenn bei der Verbindung ein Fehler aufgetreten ist.
ConnectionString verändern
Die oben erwähnten Ereignisse WillConnect und ConnectComplete scheinen wie dazu
geschaffen, um vor dem Verbindungsaufbau die ConnectionString-Zeichenkette zu
verändern und nach dem Verbindungsaufbau wieder auszulesen (also etwa, um Server- oder Login-Name in der Registrierdatenbank zu speichern).
Leider gibt es dabei massive Probleme. Der Versuch, ConnectionString in diesen Ereignisprozeduren durch eine einfache Zuweisung einer neuen Zeichenkette zu verändern, führt zu mysteriösen Ergebnissen. Veränderungen werden zum Teil überhaupt
nicht und zum Teil anders als geplant ausgeführt.
Besser klappt die zweite Möglichkeit, um ConnectionString zu verändern, nämlich
durch die Verwendung von Properties – allerdings mit einer Ausnahme. Der Versuch,
dem Parameter Integrated Security eine leere Zeichenkette zuzuweisen (um so anzudeuten, daß keine NT-Authentifizierung erwünscht ist), führt dazu, daß der entsprechende Teil in ConnectionString so aussieht:
Provider=SQLOLEDB.1;Integrated Security="";Persist Security ...
Das Problem sind die beiden Anführungszeichen, die vom Treiber so interpretiert
werden, als wäre eine NT-Authentifizierung erwünscht. Damit der Treiber kapiert,
daß diese Authentifizierung nicht erwünscht ist, muß das Schlüsselwort als Ganzes
fehlen (was aber nicht möglich ist, weil sich die ConnectionString-Zeichenkette nicht
entsprechend ändern läßt), oder der fehlende Wert muß ohne Anführungszeichen
angegeben werden (also Integrated Security=;) – und das vereitelt Properties.
6.5 Fehlerabsicherung
219
Nun, es gibt auch hierfür eine Lösung: Es muß dafür gesorgt werden, daß der Parameter Integrated Security gar nicht erst in die ConnectionString-Zeichenkette gelangt,
wenn keine NT-Authentifizierung erwünscht ist. Dazu geben Sie im DATALINK-Dialog
des DE-Designers an, daß Sie die NT-Authentifizierung nicht verwenden möchten
(auch wenn Sie eigentlich der gegenteiligen Meinung sind).
HINWEIS
Der Nachteil dieser Strategie besteht darin, daß jetzt bei jedem Zugriff auf ein
Objekt des SQL Servers (via DE-Designer in der VB-Entwicklungsumgebung)
der Login-Dialog erscheint und Sie entweder VERTRAUTE VERBINDUNG anklicken
oder ein Paßwort angegeben müssen. Es geht auch bequemer:
Geben Sie im Eigenschaftsfenster das Paßwort als DesignPassword an und stellen Sie DesignSaveAuthentification auf True. Aber jetzt wird das Paßwort im
Klartext (!) im Eigenschaftsfenster angezeigt und unverschlüsselt in den Projektdateien gespeichert – eine Einladung an alle, die das Paßwort gerne wissen
möchten. Soviel mangelndes Sicherheitsbewußtsein bei Microsoft ist wirklich
haarsträubend. Warum wird das Paßwort im Eigenschaftsfenster nicht durch
Sternchen dargestellt? Und warum wird nicht ein – egal wie primitiver – Verschlüsselungsalgorithmus eingesetzt, bevor das Paßwort in der Designer-Datei
gespeichert wird? (Zum Glück wird das Paßwort wenigstens nicht in der
*.exe-Datei gespeichert – das ist der Grund, weswegen zwischen Design- und
Run-Eigenschaften unterschieden wird.)
ANMERKUNG
Im Programm verwenden Sie dann natürlich doch die NT-Authentifizierung als Defaulteinstellung beim ersten Start (d.h., wenn noch keine Einträge in der Registrierdatenbank gespeichert sind). Entscheidend ist nur, daß die im Programm gespeicherte
Zeichenkette das Schlüsselwort Integrated Security nicht enthält – dann kann dieses
Schlüsselwort bei Bedarf eingefügt werden, stört aber im umgekehrten Fall nicht. Entsprechende WillConnect- und ConnectComplete-Ereignisprozeduren sind im Rahmen
des Beispielprogramms abgedruckt.
Es sind Kleinigkeiten wie das oben beschriebene Problem, die die Programmierung mit VB so unglaublich mühsam und ineffizient machen. Dabei ist hier
noch nicht einmal ganz klar, wo der Fehler liegt. Im OLE-DB-Treiber, der ""
falsch interpretiert? Oder in der Properties-Methode, die leere Zeichenketten
falsch speichert? Letztlich fragt keiner danach. Letztlich zählt das Ergebnis am
Ende eines Arbeitstags. Wenn da nicht mehr bleibt als ein abenteuerlicher Hack,
um eine Microsoft-Schlamperei zu umgehen, ist das erbärmlich!
Beispielprogramm
Das Beispielprogramm besteht aus dem DE-Designer und zwei Formularen, dem Datenformular und einem Infofenster. Als Startobjekt in PROJEKT|EIGENSCHAFTEN| ALLGEMEIN gilt das Datenformular. Da sich in diesem Formular Steuerelemente befinden,
220
6 Connection-Objekt (Verbindungsaufbau)
die an DE-Objekte gebunden sind, beginnt die Programmausführung mit den DEEreignisprozeduren. In diesen Prozeduren befindet sich der gesamte verbindungsrelevante Code.
In DataEnvironment_Initialize wird das Infofenster geöffnet. (Das Datenformular ist zu
diesem Zeitpunkt noch unsichtbar.)
' Connection\DataEnvironment\Failsafe\Failsafe.vbp
' Connection\DataEnvironment\Failsafe\de.dsr
' Programmstart
Private Sub DataEnvironment_Initialize()
formConnectinfo.Show
formConnectinfo.Refresh
End Sub
connPubs_WillConnect liest wie in den Beispielprogrammen der vorherigen Abschnitte
die zuletzt gültigen Verbindungsdaten aus der Registrierdatenbank und trägt sie via
Properties in die ConnectionString-Zeichenkette ein. Das Programm geht davon aus, daß
ConnectionString ursprünglich keinen Integrated-Security-Eintrag enthält und fügt diesen nur dann ein, wenn er tatsächlich benötigt wird (Begründung siehe oben).
' zuletzt verwendete Einstellungen aus Registrierdatenbank lesen
Private Sub connPubs_WillConnect(ConnectionString$, userID$,
Password$, Options&, adStatus As ADODB.EventStatusEnum,
ByVal pConnection As ADODB.Connection)
Dim dataSource$, intSecurity$, useID$
dataSource = GetSetting("mk", "sqlconnect", "DataSource", "MARS")
intSecurity = GetSetting("mk", "sqlconnect", "IntSecurity", "SSPI")
useID = GetSetting("mk", "sqlconnect", "userID", "")
With pConnection
.Properties("Data Source") = dataSource
If intSecurity <> "" Then
.Properties("Integrated Security") = intSecurity
End If
.Properties("User ID") = useID
End With
End Sub
Mit Abschluß der Initialisierung wird connPubs_ConnectComplete ausgeführt. Wenn
alles geklappt hat, werden die neuen Verbindungseckdaten in der Registrierdatenbank gespeichert. Außerdem wird das Infofenster geschlossen. Ist dagegen ein
Fehler vorgefallen (d.h., der Anwender hat im SQL-Login-Dialog resigniert und ABBRUCH angeklickt), wird das Programm beendet.
6.6 Asynchroner Verbindungsaufbau
221
Private Sub connPubs_ConnectComplete(ByVal pError As ADODB.Error,
adStatus As ADODB.EventStatusEnum,
ByVal pConnection As ADODB.Connection)
If adStatus = adStatusOK Then
' tatsächlich verwendete Einstellungen in Registrierdatenbank
' speichern
With pConnection
SaveSetting "mk", "sqlconnect", "DataSource", _
.Properties("Data Source")
SaveSetting "mk", "sqlconnect", "IntSecurity", _
.Properties("Integrated Security")
SaveSetting "mk", "sqlconnect", "userID", _
.Properties("User ID")
End With
Unload formConnectinfo
Else
' Programmabbruch
MsgBox "Es ist nicht gelungen, eine Verbindung zur Datenbank" & _
" herzustellen. Das Programm wird beendet."
End
End If
End Sub
6.6
Asynchroner Verbindungsaufbau
Wenn Sie die Datenbankverbindung mit ADO-Code herstellen oder die DataEnvironment-Initialisierung per Programmcode auslösen, können Sie bei der Open-Methode
Options:=adAsyncConnect angeben. Das bewirkt, daß die VB-Programmausführung
unmittelbar fortgesetzt wird, ohne zu warten, bis der Verbindungsaufbau klappt oder
abgebrochen wird. Sobald der Verbindungsaufbau abgeschlossen, tritt ein ConnectComplete-Ereignis ein. Unabhängig davon verrät auch der Zustand der StateEigenschaft des Connnection-Objekts, ob die Verbindung bereits verfügbar ist.
Es kann zwei Gründe geben, die Verbindung asynchron zu öffnen.
• Parallelverarbeitung: Bis das Programm Daten aus der Datenbank benötigt, hat es
noch diverse andere (rechen- oder zeitintensive) Aufgaben zu erledigen. Diese
werden während des Verbindungsaufbaus durchgeführt. Die Gesamtwartezeit, bis
das Programm benutzbar ist, wird so minimiert.
222
6 Connection-Objekt (Verbindungsaufbau)
• Bedienungskomfort: Der Verbindungsaufbau dauert erfahrungsgemäß ziemlich
lang (z.B. weil Ihr Netzwerk notorisch überlastet ist). Der asynchrone Verbindungsaufbau ermöglicht es, dem Benutzer ein Feedback zu geben (z.B. in Form eines Zustandsbalkens, der bis zum Connection Timeout allmählich wächst), so daß
klar ist, daß das Programm (noch) nicht abgestürzt ist. Außerdem bekommt der
Anwender dank eines ABBRUCH-Buttons die Möglichkeit, den Vorgang vorzeitig zu
beenden.
Das erste dieser beiden Szenarien läßt sich relativ problemlos in die Tat umsetzen.
Probleme bereitet allerdings das zweite Szenario. Microsoft hat sich offensichtlich zu
wenig Gedanken darüber gemacht, wie ein asynchroner Verbindungsversuch gestoppt werden kann. Dafür ist zwar die Methode Cancel vorgesehen, aber sobald Sie
die ausführen, ist das Programm blockiert, bis die durch Connection Timeout angegebene Zeitspanne abgelaufen ist; die Asynchronität endet also mit Cancel.
HINWEIS
Übrigens scheitert auch der Versuch, das Programm mit End zu beenden, das Connection-Objekt durch eine Nothing-Zuweisung zu löschen etc. Kurz und gut: Es ist gegenwärtig unmöglich, einen einmal gestarteten Verbindungsversuch zum SQL Server
vorzeitig zu stoppen. Das Ziel, eine intuitivere oder komfortablere Bedienung des
Programms zu ermöglichen, ist also nur sehr eingeschränkt erreichbar.
Zur Wiederholung nochmals ein paar Worte zum ConnectComplete-Ereignis:
dieses tritt nicht nur bei asynchronem, sondern auch bei synchronem Verbindungsaufbau auf, und es tritt auch dann auf, wenn der Verbindungsaufbau
scheitert! Diesen Fall können Sie erkennen, indem Sie den adStatus-Parameter
auswerten, der an die Prozedur übergeben wird.
Beispielprogramm
Das Beispielprogramm zeigt während des Verbindungsversuchs das nun schon bekannte Infofenster an – diesmal allerdings ergänzt um einen Zustandsbalken, der die
Wartezeit während des ersten Verbindungsversuchs erträglicher macht. Darüber hinaus wird unmittelbar nach der asynchronen Open-Methode die Prozedur DoSomeWork
gestartet, die eine einminütige Berechnung simuliert. Diese Berechnung wird parallel
zum Verbindungsaufbau durchgeführt. Das Programm verwendet übrigens absichtlich einen falschen Server-Namen, um so den Umgang mit Verbindungsfehlern zu
demonstrieren.
Das Beispielprogramm beschränkt sich auf ADO-Code (kein DE-Designer, kein AdodcSteuerelement) und besteht aus zwei Formularen: dem Datenformular und dem Infofenster. Die Programmausführung beginnt in Form_Load des Datenformulars. Das
Connection-Objekt conn ist mit der Option WithEvents deklariert, damit dessen Ereignisse verarbeitet werden können.
6.6 Asynchroner Verbindungsaufbau
223
In Form_Load wird als erstes das Datenformular unsichtbar gemacht (Visible=False)
und das Infofenster angezeigt. Dabei wird an die generelle Variable des Infofensters
die Zeit für den Connection Timeout übergeben. (Diese Zeit wird für die Skalierung des
Zustandsbalkens benötigt.) Die Zeit ist aus Demonstrationszwecken bewußt kurz gehalten.
Bild 6.8: Während des asynchronen Verbindungsaufbau
wird der Zustandsbalken regelmäßig aktualisiert
Die folgenden Zeilen lesen die Verbindungseckdaten aus der Registrierdatenbank und
sind mittlerweile schon vertraut. Zu Testzwecken wird für dataSource allerdings eine
offensichtlich falsche Angabe verwendet. (Entfernen Sie diese Zeile, wenn Sie das
Codefragment in eigenen Programmen einsetzen möchten!)
Die Fehlerabsicherung bei Open Options:=adAsyncConnect ist eigentlich nicht unbedingt notwendig. Da Verbindungsfehler hier nicht auftreten (sondern erst in der ConnectionComplete-Prozedur), kann es nur zu einem Fehler kommen, wenn die ConnectionString-Zeichenkette syntaktisch falsch ist.
' Connection\Async\Async.vbp
' Connection\Async\formMain.frm
Dim rec As Recordset
Public WithEvents conn As Connection
Private Sub Form_Load()
Dim dataSource$, intSecurity$, userID$
Dim timeout&
' dieses Fenster unsichtbar machen, bis Verbindungsaufbau
' abgeschlossen ist
Visible = False
' Infofenster anzeigen
timeout = 5
formConnectinfo.timeout = timeout
formConnectinfo.Show
' Server-Name und Authentifikations-Infos aus Registry lesen
' Defaultwerte (für das erste Mal): "MARS", "SPPI", ""
dataSource = GetSetting("mk", "sqlconnect", "DataSource", "MARS")
224
6 Connection-Objekt (Verbindungsaufbau)
intSecurity = GetSetting("mk", "sqlconnect", "IntSecurity", "SSPI")
userID = GetSetting("mk", "sqlconnect", "userID", "")
' Verbindungsfehler durch falschen Server-Namen simulieren
dataSource = "dummy to test async connect"
' Verbindung asynchron aufbauen
Set conn = New Connection
conn.ConnectionString = "Provider=SQLOLEDB;" & _
"Integrated Security=" & intSecurity & ";" & _
"Data Source=" & dataSource & ";" & _
"User ID=" & userID & ";" & _
"Connect Timeout=" & timeout & "; " & _
"Initial Catalog=pubs;Prompt=completerequired;" & _
"Persist Security Info=false"
On Error Resume Next
conn.Open Options:=adAsyncConnect
If Err Then
' hier sollte es nur zu einem Fehler kommen, wenn der
' ConnectionString syntaktisch falsch ist
MsgBox "Syntaxfehler in ConnectionString oder unbekannter " & _
"Fehler. Das Programm wird beendet."
End
End If
' während (und nach) des Verbindungsaufbaus Berechnung durchführen
DoSomeWork
End Sub
Form_Load endet mit dem Aufruf von DoSomeWork. Im Beispielprogramm dient diese
Prozedur nur dazu, für eine Minute eine Berechnung zu simulieren und so zu zeigen,
daß tatsächlich parallel zum Verbindungsaufbau eine andere Tätigkeit durchgeführt
werden kann.
Wenn Sie statt dieses Beispielscodes wirklich eine Aufgabe erledigen möchten, achten
Sie bitte darauf, daß DoEvents mindestens fünf Mal pro Sekunde ausgeführt werden
muß (damit die Parallelität des Programms erhalten bleibt, also etwa der Zustandsbalken im Infofenster regelmäßig aktualisiert wird), daß DoEvents aber nicht so oft wie
hier ausgeführt wird (das kostet eine Menge Zeit, um welche die eigentliche Berechnung langsamer wird).
Private Sub DoSomeWork()
'für eine Minute harte Arbeit simulieren
Dim x As Double, endtime As Date
endtime = Now + TimeSerial(0, 0, 60) ' 60 Sekunden
Do
x = Sin(Rnd)
DoEvents
Loop Until Now > endtime
End Sub
6.6 Asynchroner Verbindungsaufbau
225
In conn_ConnectComplete wird zuerst das Infofenster geschlossen. Im Fall eines Verbindungsfehlers wird das Programm auch gleich beendet. Andernfalls werden das
Datenformular angezeigt, die Verbindungsdaten in der Registrierdatenbank gespeichert und die Abfrage für die im Textfeld angezeigten Verlagsnamen durchgeführt.
Der Code unterscheidet sich nicht von dem aus Abschnitt 6.5.1, weswegen auf einen
nochmaligen Abdruck verzichtet wurde.
Private Sub conn_ConnectComplete(ByVal pError As ADODB.Error,
adStatus As ADODB.EventStatusEnum,
ByVal pConnection As ADODB.Connection)
Dim dataSource$, intSecurity$, userID$
Unload formConnectinfo
' Infoformular schließen
If adStatus <> adStatusOK Then
' keine Verbindung --> Programm beenden
MsgBox "Die Verbindung konnte nicht hergestellt werden. " & _
"Das Programm wird beendet."
End
Else
' es hat geklappt: Info-Fenster schließen, dieses Fenster
' anzeigen
Unload formConnectinfo
Show
' tatsächlich verwendete Verbindungsdaten in Registry speichern
... wie in Abschnitt 6.5.1
' jetzt kann die Verbindung genutzt werden
... wie in Abschnitt 6.5.1
End If
End Sub
Noch eine Anmerkung zu cmdEnd_Click: Diese Prozedur wird ausgeführt, wenn der
Anwender den ENDE-Button des Datenformulars anklickt. Um das Programm zu beenden, muß hier tatsächlich End ausgeführt werden. (Unload Me reicht nicht aus, weil
ja vielleicht DoSomeWork und damit auch Form_Load noch ausgeführt wird. Unload
wird aber erst ausgeführt, nachdem Form_Load abgeschlossen ist.)
Private Sub cmdEnd_Click()
End
End Sub
Infofenster
Nun zum Code im Infofenster: In dessen Form_Load werden einige Variablen sowie
das Timer-Steuerelement initialisiert. In der Folge wird alle 100 ms die Prozedur
Timer1_Timer ausgeführt, um die Länge des Zustandsbalkens neu festzusetzen.
226
6 Connection-Objekt (Verbindungsaufbau)
' Connection\Async\formConnectinfo.frm
Public timeout
Dim starttime As Date, endtime As Date
' Initialisierung des Formulars
Private Sub Form_Load()
If timeout = 0 Then timeout = 15
' default
Timer1.Enabled = True
starttime = Now
endtime = starttime + TimeSerial(0, 0, timeout)
ProgressBar1.Value = 0
Refresh
End Sub
' ProgressBar alle 100 ms aktualisieren
Private Sub Timer1_Timer()
Dim timeNow As Date
timeNow = Now
If timeNow >= endtime Then
Timer1.Enabled = False
ProgressBar1.Value = 100
Exit Sub
End If
ProgressBar1.Value = _
100 * CDbl(timeNow - starttime) / CDbl(endtime - starttime)
Refresh
End Sub
6.7
DataLink-Dateien
6.7.1
DataLink-Dateien in VB-Programmen nutzen
Die OLE-DB-Bibliothek sieht vor, die Verbindungseigenschaften (also den Inhalt von
ConnectionString bzw. das, was im DATALINK-Dialog eingestellt wird) in sogenannten
*.udl-Dateien zu speichern. (UDL steht übrigens für universal data link.) Allerdings ist
diese Möglichkeit sehr schlecht dokumentiert. Zudem wird sie von der VB-Entwicklungsumgebung nicht konsequent unterstützt. Einzig der Eigenschaftsdialog des
Adodc-Steuerelements sieht vor, als Datenquelle eine OLE-Datenbankdatei zu verwenden (und damit ist eine *.udl-Datei gemeint).
Bevor Details im Umgang mit solchen Dateien beschrieben werden, kurz einige Worte
zu den Möglichkeiten, die sich daraus ergeben. In den bisherigen Beispielprogrammen
waren die Verbindungseckdaten fest kodiert. Damit das Programm auch dann noch
funktioniert, wenn sich beispielsweise der Name des Datenbank-Servers ändert,
mußte Prompt=completerequired eingesetzt werden. Damit der Anwender nicht bei je-
6.7 DataLink-Dateien
227
dem Start mit denselben Abfragen belästigt wird, war ein recht aufwendiger Code
erforderlich, um die Eckdaten in der Registrierdatenbank zu speichern bzw. wieder
von dort zu lesen.
Mit *.udl-Dateien können Sie nun die Verbindungsinformationen von der *.exeDatei des Programms trennen. Der Anwender kann die *.udl-Datei durch einen
Doppelklick im Explorer bearbeiten. (Es erscheint dann einfach derselbe DATALINKDialog wie im Datenansichtfenster.) Oder Sie können bei Problemen, etwa wenn ein
zusätzlicher ConnectionString-Parameter gesetzt werden soll, einfach eine neue Version
der *.udl-Datei zur Verfügung stellen, ohne gleich das ganze Programm neu kompilieren und weitergeben zu müssen.
*.udl-Dateien erzeugen
Nichts ist leichter als das! Starten Sie den Windows Explorer und führen Sie darin
DATEI|NEU|MICROSOFT DATENLINK aus. Damit wird im aktuellen Verzeichnis eine vorerst fast leere *.udl-Datei erzeugt. Durch einen Doppelklick aktivieren Sie dann den
aus der VB-Entwicklungsumgebung vertrauten DATALINK-Dialog und stellen die Verbindungseigenschaften ein. Das Ergebnis ist eine Datei, die folgendermaßen aussieht:
[oledb]
; Everything after this line is an OLE DB initstring
Provider=SQLOLEDB.1;Integrated Security=SSPI;
Persist Security Info=False;Initial Catalog=pubs;Data Source=MARS
Die beiden letzten Zeilen sind eigentlich nur eine einzige lange Zeile, die hier aus
Platzgründen getrennt wurde.
HINWEIS
*.udl-Dateien werden generell im Unicode-Format gespeichert. Unter NT
können Sie solche Dateien mit dem Notepad-Editor verändern. Unter Windows
95 müssen Sie dagegen WinWord verwenden (weder Notepad noch Wordpad
kommen unter Windows 95 mit Unicode zurecht).
Wenn Sie die Datei in VB direkt lesen/schreiben möchten, müssen Sie die Unicode-kompatiblen Methoden der FSO-Bibliothek verwenden (File System Objects, Teil der Microsoft Scripting Runtime Library).
Connection-Objekt aus *.udl-Datei erzeugen
Auch das bereitet keinerlei Probleme: Sie geben als ConnectionString einfach nur File
Name=name.udl an. Im Beispiel unten wird die *.udl-Datei aus dem aktuellen Verzeichnis geladen, dessen Pfad mit App.Path ermittelt wird. (Eigentlich können Sie auf
die Pfadangabe verzichten – *.udl-Dateien im aktuellen Verzeichnis findet das Programm selbständig.)
228
6 Connection-Objekt (Verbindungsaufbau)
In ConnectionString wurde bewußt kein Provider angegeben. Damit wird zur Auswertung der Zeichenkette automatisch der MSDASQL-Treiber verwendet, für den das
Schlüsselwort File Name definiert ist. Welcher Provider dann tatsächlich für den Datenzugriff verwendet wird, hängt vom Inhalt der *.udl-Datei an.
VERWEIS
' Connection\Datalink\Intro\Intro.vbp
Dim conn As Connection
Private Sub Form_Load()
Dim ergebnis&
Set conn = New Connection
conn.ConnectionString = "File Name=" & App.Path & "\pubs.udl"
conn.Open
' Verbindung nutzen
... Code wie in Abschnitt 6.2.1
End Sub
In *.udl-Dateien sollten generell keine Paßwörter gespeichert werden. (Das ist
ein Sicherheitsrisiko. Paßwörter werden unverschlüsselt gespeichert!) Wenn der
Login trotz fehlenden Paßworts klappen, müssen Sie ConnectionString um
"Prompt=completerequired" erweitern. Wenn jetzt Login-Informationen fehlen
(etwa beim Zugriff auf eine Datenbank des SQL Servers, wenn dessen Authentifizierungsverfahren verwendet wird), erscheint automatisch ein Dialog, in
dem ein Paßwort angegeben werden kann:
conn.ConnectionString = "File Name=" & App.Path & _
"\pubs.udl" & ";Prompt=completerequired"
*.udl-Datei zusammen mit dem DataEnvironment-Designer verwenden
Der DataEnvironment-Designer sieht eigentlich keine Möglichkeit vor, *.udl-Dateien
zu verwenden. KnowledgeBase-Artikel Q189680 verrät, wie es dennoch geht: Sie klikken im DE-Designer das Connection-Objekt an und geben dann im Eigenschaftsfenster
für die ConnectionSource-Eigenschaft die Zeichenkette File Name=name.udl an. Das
war's!
6.7.2
OLE DB Service Component Library (MSDASC)
In meinem VB6-Buch hatte ich noch geschrieben, es gäbe kein DataLink-Objekt. Knowledge Base Artikel Q193128 hat mich mittlerweile eines besseren belehrt. Dieser Artikel zeigt nämlich, wie die Microsoft OLE DB Service Component 1.0 Type Library (Abkürzung MSDASC, wobei DA dann wohl für Data Access steht) dazu genutzt werden
kann, den DATALINK-Dialog per Programmcode aufzurufen.
6.7 DataLink-Dateien
229
PromptNew
Der Code für den Verbindungsaufbau zu einer beliebigen Datenbank läßt sich damit
auf zwei Zeilen reduzieren:
Dim dl As New DataLinks, conn As New Connection
conn.Open dl.PromptNew
Warum das Objekt DataLinks und nicht DataLink heißt (also Singular), weiß allein
Microsoft. Sicher ist, daß es sich nicht um eine Aufzählung handelt. Die Methode
PromptNew dieses Objekts zeigt den bekannten DATALINK-Dialog an. Als Resultat erhalten Sie die ConnectionString-Zeichenkette, auf deren Basis Sie dann die Verbindung
zur ausgewählten Datenbank herstellen können. (Wenn der Anwender im DATALINKKatalog ABBRECHEN anklickt, tritt ein auffangbarer Fehler auf.)
PromptEdit
Einmal auf die richtige Fährte gebracht, ist der Entdeckergeist natürlich geweckt! Bietet die MSDASC-Bibliothek vielleicht noch andere nützliche Funktionen? Ein paar
Experimente mit der (wie PromptNew nicht dokumentierten) Methode PromptEdit des
DataLinks-Objekts ergaben folgende Funktion:
An die Methode wird ein nicht (!) geöffnetes Connection-Objekt übergeben. Es erscheint nun der DATALINK-Dialog, in dem sämtliche Eigenschaften der Verbindung mit
Ausnahme des Providers verändert werden können. Die Methode liefert True oder
False zurück, je nachdem, ob die Verbindungseigenschaften verändert wurden oder
nicht.
Eine mögliche Anwendung: Wenn der Versuch, eine Verbindung zur Datenbank aufzubauen, aus irgendeinem Grund scheitert, bietet dieser Dialog dem Anwender die
Möglichkeit, die Parameter nachzujustieren. Die folgenden Zeilen zeigen ein derartiges Codefragment (allerdings mit noch unzureichender Fehlerabsicherung).
Dim conn As New Connection
Dim dl As New DataLinks
Dim result&
conn.ConnectionString = "..."
On Error Resume Next
conn.Open
If Err Then
result = dl.PromptEdit(conn)
If result = True Then ' neuer Versuch
conn.Open
End If
End If
HINWEIS
230
6 Connection-Objekt (Verbindungsaufbau)
Beachten Sie, daß PromptEdit nicht dazu verwendet werden kann, die Eigenschaften einer existierenden Verbindung zu ändern! Wenn Sie es dennoch versuchen, kommt es zu diversen Fehlermeldungen. Ob eine Verbindung aktiv ist,
können Sie mit conn.State = adStateOpen testen.
Weitere MSDASC-Objekte
Der Objektkatalog beweist, daß die MSDASC-Bibliothek diverse weitere Objekte oder
Methoden kennt. Die Methoden des MSDAINITIALIZE-Objekt sind sogar in der
MSDN-Library dokumentiert (allerdings für C++):
PLATFORM SDK|DATA ACCESS SERVICES|DATA ACCESS SDK|
OLE DB|DATA LINK API REFERENCE
Der Versuch, die Methoden in einem VB-Programm sinnvoll anzuwenden, ist aber
gescheitert.
Beispielprogramm
Mit dem folgenden Beispielprogram können Sie dank DATALINK-Dialog eine beliebige
Datenbank auswählen. Das Programm zeigt dann im Textfeld eine Liste aller Tabellen
dieser Datenbank an. Dazu wird die selten eingesetzte Methode OpenSchema des Connection-Objekts verwendet. Das so geöffnete Recordset-Objekt enthält dann zeilenweise
Informationen zum Datenbankschema (wobei der Typ der Informationen mit dem
OpenSchema-Parameter ausgewählt wird).
' Connection\DataLink\Msdasc\sample.vbp
Private Sub cmdSchema_Click()
Dim conn As New Connection, rec As Recordset
Dim dl As New DataLinks
Dim conStr$
On Error Resume Next
conStr = dl.PromptNew
If Err Then Exit Sub
conn.Open conStr
' Liste aller Tabellen der Datenbank anzeigen
Set rec = conn.OpenSchema(adSchemaTables)
txtSchema = conStr + vbCrLf + vbCrLf
While Not rec.EOF
txtSchema.Text = txtSchema.Text & _
"Table name: " & rec!TABLE_NAME & vbCrLf & _
" Type: " & rec!TABLE_TYPE & vbCrLf & vbCrLf
rec.MoveNext
Wend
End Sub
6.8 Transaktionen
231
Bild 6.9: Die Liste aller Tabellen der ausgewählten Datenbank
6.8
Transaktionen
HINWEIS
Der Begriff Transaktion beschreibt in der Datenbanken-Nomenklatur eine Gruppe von
Kommandos, die gemeinsam ausgeführt werden. Scheitert eine Transaktion (oder
wird sie gezielt abgebrochen), bleibt die Datenbank in einem konsistenten Zustand,
wobei kein einziges Kommando der Transaktion ausgeführt wurde.
Dieser Abschnitt bezieht sich auf Transaktionen innerhalb einer Datenbank. Der
Transaktionsbegriff wird häufig auch für Operation verwendet, die (rechnerübergreifend) in Multi-Tier- oder Internet-Systemen stattfinden bzw. die
Daten aus mehreren Datenbanken betreffen. In solchen Fällen sind die Transaktionsmechanismen eines einzelnen Datenbanksystems unzureichend. Zur
Absicherung solcher Operationen muß ein eigener Transaktions-Server eingesetzt werden, was aber nicht Thema dieses Buchs ist.
Transaktionen auf Datenbankebene haben gegenüber nicht abgesicherten Einzeloperationen zwei Vorteile:
• Datenbankoperationen werden sicherer ausgeführt. Dank der Transaktionen ist
selbst bei einem Systemabsturz oder Stromausfall sichergestellt, daß entweder alle
Operationen der Transaktion vollständig ausgeführt wurden oder aber gar keine.
• Datenbankoperationen werden schneller ausgeführt, weil mehrere Kommandos
zuerst zwischengespeichert und dann auf einmal ausgeführt werden; das spart Zeit
bei der Datenübertragung, insbesondere im Netzwerkbetrieb.
Transaktionen werden von fast allen Datenbanksystemen unterstützt – allerdings
nicht immer auf die gleiche Weise. Bevor Sie eine Transaktion durchführen, sollten Sie
testen, ob die Eigenschaft Properties("Transaction DDL") existiert und einen Wert größer als 0 enthält:
232
6 Connection-Objekt (Verbindungsaufbau)
Dim transSupport As Boolean
On Error Resume Next
transSupport = False
If conn.Properties("Transaction DDL") > 0 Then transSupport = True
On Error GoTo 0
'Fehlertoleranz wieder deaktivieren
Vor Beginn der Änderungsarbeiten wird die Connection-Methode BeginTrans ausgeführt. Ab diesem Zeitpunkt werden alle Datenbankoperationen zwischengespeichert,
aber noch nicht tatsächlich in der Datenbank ausgeführt. Mit RollbackTrans können
Sie sämtliche Kommandos ab BeginTrans widerrufen. Erst CommitTrans bestätigt die
durchgeführten Änderungen. Die drei Methoden BeginTrans, CommitTrans und RollbackTrans gelten für alle über das Connection-Objekt durchgeführten Operationen (und
nicht nur für einzelne Tabellen).
Die drei Methoden werden vor allem für Änderungen angewendet, die gleichzeitig
mehrere Teile einer Datenbank betreffen und nur als Gesamtheit oder gar nicht
durchgeführt werden. Ein gutes Beispiel dafür wären der Buchungs- und Gegenbuchungsvorgang in einem Buchhaltungsprogramm. Falls bei der Gegenbuchung ein
Fehler auftritt, wird auch die erste Buchung rückgängig gemacht.
conn.BeginTrans
ok = Buchung(kontonr1, wert) 'Unterprogramm zur Buchung
If ok Then
ok = Gegenbuchung(kontonr2, wert)
If ok Then
conn.CommitTrans
Else
conn.RollbackTrans
End If
Else
conn.RollbackTrans
End If
Bei vielen Datenbanksystemen darf BeginTrans, CommitTrans und RollbackTrans mehrfach verschachtelt werden. BeginTrans liefert dabei die aktuelle Transaktionsebene als
Rückgabewert. Es ist unbedingt erforderlich, daß zu jedem BeginTrans-Befehl der dazugehörende Commit- oder RollbackTrans-Befehl ausgeführt wird – auch dann, wenn
irgendwo ein unvorhergesehener Fehler auftritt!
Nach Abschluß der jeweiligen Methoden werden BeginTransComplete-, BeginTransComplete- und RollbackComplete-Ereignisse ausgelöst. Auch die Parameter dieser
Prozeduren geben Rückschluß über die aktuelle Transaktionsebene.
Manche Datenbanksysteme können mit jedem CommitTrans bzw. RollbackTrans automatisch eine neue Transaktion starten. BeginTrans muß dann nicht mehr ausgeführt
werden! Dieses Verhalten wird durch die Attributes-Eigenschaft gesteuert. In der
Defaulteinstellung 0 muß BeginTrans explizit ausgeführt werden.
6.8 Transaktionen
233
TIP
Die Isolation-Eigenschaft beschreibt, wie sich die Datenbank verhält, wenn mehrere
Transaktionen quasi gleichzeitig durch mehrere Datenbank-Clients durchgeführt
werden. Der Defaultwert adXactReadCommitted bedeutet, daß Veränderungen durch
eine Transaktion für andere Clients erst nach Abschluß der Transaktion sichtbar werden.
Während der Durchführung einer Transaktion werden Teile der Datenbank gegenüber anderen Prozessen blockiert. Alle anderen Programme müssen also
warten, bis die Transaktion beendet ist. Aus diesen Gründen sollten Sie allzu
komplexe oder lang andauernde Transaktionen unbedingt vermeiden!
Transaktionen mit Server-seitigem Cursor ('catastrophic failures')
Wenn Sie mit Recordsets Server-seitigen Cursor und dem SQLOLEDB-Provider arbeiten, wird der Cursor sowohl durch CommitTrans als auch durch RollbackTrans automatisch geschlossen (was selten erwünscht ist). Beim nächsten Versuch, auf das RecordsetObjekt zuzugreifen, tritt ein origineller 'catastrophic failure' auf.
Dieses Verhalten können Sie vermeiden, indem Sie vor dem Öffnen des RecordsetObjekts die dynamischen Recordset-Eigenschaften Properties("Preserve On Commit")
und Properties("Preserve On Abort") auf True setzen. Sie müssen das Recordset-Objekt
jetzt unbedingt mit Open öffnen. Execute erzeugt nämlich ein neues Default-Recordset
und ignoriert allfällige vorherige Einstellungen. (Die folgenden Zeilen gehen wie üblich davon aus, daß rec, comm und conn geeignete Recordset-, Command- und Connection-Objekte sind.)
VERWEIS
rec.Properties("Preserve On Commit") = True
rec.Properties("Preserve On Abort") = True
comm.Parameters(0) = ...
rec.Open comm, conn
Eine Diskussion über die diversen Recordset-Cursortypen finden Sie ab Seite
256. Weitere Informationen zum hier beschriebenen Cursorproblem finden Sie
im Knowledge-Base-Artikel Q187942 in der MSDN-Library).
Tatsächlich bleibt das Recordset nun nach CommitTrans verwendbar. Weniger gut sieht
es aus, wenn Sie die Transaktion mit RollbackTrans abbrechen. Zwar wird der Cursor
offenbar wirklich nicht geschlossen, aber ADO meldet sich nun beim nächsten Update
mit dem sehr informativen Text 'es sind einige Fehler aufgetreten', und damit sind Sie
so weit wie zuvor. Die einzig mögliche Abhilfe scheint darin zu bestehen, das betroffene Recordset-Objekt nach RollbackTrans zu schließen und neu zu öffnen. (Oder Sie
warten auf die nächste ADO-Version und hoffen, daß dort alles besser wird ...)
234
6.9
6 Connection-Objekt (Verbindungsaufbau)
Errors-Aufzählung
Das Errors-Aufzählungsobjekt, das über die gleichnamige Eigenschaft des ConnectionObjekts angesprochen werden kann, hat eigentlich nichts mit dem Verbindungsaufbau
zu tun. Bei ADO gilt aber die Philosophie, daß alle Fehler (auch wenn diese beim Umgang mit einem Command- oder Recordset-Objekt auftreten) zentral verwaltet und dem
zugrundeliegenden Connection-Objekt zugeordnet werden. Aus diesem Grund wird
Errors hier beschrieben.
Wenn bei der Ausführung von ADO-Code ein Fehler auftritt, erfolgt die Benachrichtigung über die üblichen VB-Fehlerbehandlungsmechanismen – d.h., der Fehler kann
mit On Error Resume abgefangen werden, Err enthält einen Verweis auf das ErrObject.
(Bei manchen Fehlern wird unabhängig von On Error Resume eine Fehlermeldung am
Bildschirm angezeigt. Diese muß mit OK bestätigt werden, bevor der Code in der
Fehlerbehandlungsroutine ausgeführt wird. Es scheint keine Möglichkeit zu geben,
diese automatische Anzeige zu verhindern.)
Der Unterschied zwischen ADO-Fehlern und herkömmlichen VB-Fehlern besteht
darin, daß zusätzlich zu ErrObject auch die erwähnte ADO-Errors-Aufzählung zur
Verfügung steht. Der Grund für diese Doppelgleisigkeit besteht darin, daß eine einzelne ADO-Anweisung mehrere Fehlermeldungen verursachen kann, die unter Umständen von verschiedenen OLE-DB-Komponenten stammen.
Die Errors-Aufzählung kann über die gleichnamige Eigenschaft des Connection-Objekts
angesprochen werden. Außerdem wird an alle ADO-Ereignisprozeduren ein Verweis
auf das Errors-Objekt mitgeliefert. Errors.Count enthält die Anzahl der Fehlermeldungen. Errors(n) führt dann auf die entsprechenden Error-Objekte, deren Eigenschaften
Informationen über die Fehlerquelle geben sollten.
Description
HelpContext
HelpFile
NativeError
Number
Source
SQLState
Zeichenkette mit der Fehlermeldung
ID-Nummer für Hilfetext, leider meist 0
Dateiname für Hilfedatei, leider meist leer
interne Fehlernummer des Providers, manchmal 0
ADO- oder OLE-DB-Fehlernummer
Zeichenkette mit dem Namen der Komponente, die den Fehler
verursacht hat (z.B. "Microsoft OLE DB-Provider für SQL Server")
Zeichenkette, enthält bei Fehlern des Datenbank-Servers eine
fünfstellige Fehlernummer entsprechend dem gleichnamigen
SQL-Schlüsselwort (gemäß ANSI SQL-92)
Wie aus der obigen Übersicht bereits hervorgeht, enthalten nicht immer alle Eigenschaften sinnvolle Werte. (Das hängt davon ab, welche Komponente den Fehler ausgelöst hat, welcher Datenbank-Server und welcher Provider verwendet wird etc.)
Wenn Sie mit der deutschen MDAC-Version arbeiten, sind auch die ADO-Fehlermeldungen deutschsprachig. Da die Übersetzer zum Teil wohl nicht verstanden haben,
6.9 Errors-Aufzählung
235
was sie da übersetzt haben, ist das eher als Nachteil zu bewerten. Es scheint aber keine
Möglichkeit zu geben, an die englischen Originale zu gelangen.
Number ist eine 32-Bit-Zahl. Die höherwertige Hälfte gibt an, ob es sich um einen
ADO-Fehler handelt (hexadezimal &H800Axxxx) oder um einen OLE-DB-Fehler handelt (&H8004xxxx). Dieser Teil der Zahl wird auch als Windows facility code bezeichnet.
Die niederwertige Hälfe der Zahl enthält dann die eigentliche Fehlernummer. Zur
Zerlegung können Sie folgenden Code verwenden:
num = conn.Errors(i).Number
If (num And &HFFFF0000) = &H800A0000 Then
MsgBox "(ADO-Fehlernummer: " & (num And &HFFFF&)
ElseIf (num And &HFFFF0000) = &H80040000 Then
MsgBox "(OLEDB-Fehlernummer: " & (num And &HFFFF&)
Else
MsgBox "(Fehlercode (hexadezimal): " & Hex(num)
End If
Die NativeError-Nummer hängt vom Provider ab. Wenn Sie MSDASQL verwenden,
um auf den SQL Server zuzugreifen, wird via NativeError die interne Fehlernummer
des SQL Servers weitergegeben. NativeError enthält häufig nur 0 oder denselben Wert
wie Number.
VERWEIS
Es ist nicht ganz leicht, Informationen über Fehlercodes zu finden. Eine Liste
wichtiger ADO-Fehlernummer (Eigenschaft Number) finden Sie hier:
PLATFORM SDK|DATA ACCESS SERVICES|MS DATA ACCESS COMPONENTS|MS ADO PROGRAMMER´S REFERENCE|ADO API REFERENCE|ADO ERROR CODES
Beachten Sie, daß es zu einigen wenigen ADO-Fehlern adErrXxx-Konstanten
gibt (ErrorValueEnum im Objektkatalog). Damit können Sie manchmal die direkte Angabe kryptischer Fehlernummern im Programmcode zu vermeiden.
TIP
Informationen zu SQLState finden Sie, wenn Sie nach diesem Begriff in der
MSDN-Library suchen. (SQLState ist ein SQL-Schlüsselwort gemäß ANSI SQL92.)
Manchmal bekommen Sie die originelle Fehlermeldung 'es sind einige Fehler
aufgetreten', leider aber keine weiteren Informationen über die Natur dieser
Fehler. KB-Artikel Q217019 empfiehlt, in diesem Fall vom SQLOLEDB- auf den
MSDASQL-Treiber (also ODBC) umzustellen – aber diese Mühe können Sie sich
meist sparen: Auch der MSDASQL-Treiber liefert keine informativere Fehlermeldung (sofern sich der Fehler überhaupt reproduzieren läßt und das Programm wegen des veränderten Treiber nicht schon an ganz anderer Stelle Probleme verursacht ...)
TIP
236
6 Connection-Objekt (Verbindungsaufbau)
Wenn Sie mit einer Fehlernummer nichts anfangen können, verwenden Sie einfach die hexadezimale Nummer (also z.B. 80040e21) als Suchbegriff in der
MSDN-Library. Meistens haben vor Ihnen auch schon andere Leute Probleme
mit dieser Fehlernummer gehabt. Besonders aufschlußreich sind die KB-Artikel
Q197459, Q168354 und Q217019.
Neben den Error-Eigenschaften ist es in vielen Fehlerbehandlungsroutinen hilfreich,
zwei Eigenschaften des Recordset-Objekts auszuwerten: EditState gibt an, ob der Datensatz gerade verändert, neu angelegt oder gelöscht wurde. Und falls Sie mit Stapelaktualisierungen arbeiten, gibt Status noch detailliertere Informationen über den Zustand des aktuellen Datensatzes (etwa adRecConcurrencyViolation).
Code zur Auswertung des Error-Objekts
Für den Endanwender sind die kryptischen Fehlernummern und die zumeist ebenso
unverständlichen Fehlertexte ungeeignet. (Betrachten Sie Bild 6.10: Die Errors-Texte im
mittleren Teil helfen dem durchschnittlichen Anwender sicher nicht weiter. Und dabei
ist das noch der Idealfall. Es gibt auch Fehlermeldungen wie 'catastrophic failure' –
siehe auch KB Q187942 – oder 'mehrere Fehler sind aufgetreten' ohne jede weitere
Zusatzinformation.)
Im Regelfall können Sie daher auf die Anzeige der Fehlermeldungen gleich ganz verzichten. Versuchen Sie statt dessen, aus dem Kontext des Programms sinnvolle Hinweise zu geben (in der Art der Texte im oberen und unteren Teil von Bild 6.10). Optional können Sie die vollständige Fehlermeldung in eine Protokolldatei schreiben.
Bild 6.10: Eine sehr informative Fehlermeldung
Während die ADO-Fehlermeldungen also kaum für den Endanwender gedacht sind,
helfen Sie manchmal bei der Programmentwicklung weiter. Die folgende Prozedur
ErrorText (die mit kleinen Variationen in einigen Beispielprogrammen dieses Buchs
eingesetzt wird) setzt eine Zeichenkette mit allen relevanten Informationen aus Errors
zusammen. Falls die Parameter start- und endtext angegeben werden, werden diese
Zeichenketten am Anfang und am Ende eingefügt. Die resultierende Zeichenkette
kann dann mit MsgBox angezeigt werden.
6.9 Errors-Aufzählung
237
If rec.EditMode = adEditAdd Then
MsgBox ErrorText("Beim Versuch, einen neuen Datensatz " & _
"anzulegen, ist ein Fehler aufgetreten.", _
"Versuchen Sie, die Eingabe zu korrigieren oder " & _
"das Problem durch 'Abbrechen' zu lösen!"), , "Fehlermeldung"
Else ...
ErrorText geht davon aus, daß eine globale Variable conn auf das Connection-Objekt
zeigt. Ist das nicht der Fall, können Sie die Prozedur leicht durch einen zusätzlichen
Parameter zur Übergabe des Connection-Objekts ergänzen. ErrorText kommt auch mit
normalen VB-Fehlern (conn.Errors.Count=0, kein Datenbankbezug) zurecht und fügt
dann einfach Err.Description in die Zeichenkette ein.
' db-controls\bound-controls\formMain.frm
Private Function ErrorText$(Optional starttext$, Optional endtext$)
Dim msg$, i&, num&
With conn
If .Errors.Count > 0 Then
' Datenbankfehler
msg = msg & "Fehlermeldungen: " & vbCrLf
For i = 0 To .Errors.Count - 1
With .Errors(i)
' Fehlerquelle und Fehlerbeschreibung
msg = msg & "(" & i & ") "
msg = msg & .Source & ": " & .Description & " "
If .SQLState <> "" Then
msg = msg & "(SQLState: " & .SQLState & ") "
End If
' ADO/OLEDB-Fehlernummer
num = .Number
If (num And &HFFFF0000) = &H800A0000 Then
msg = msg & "(ADO-Fehlernummer: " & (num And &HFFFF&)
ElseIf (num And &HFFFF0000) = &H80040000 Then
msg = msg & "(OLEDB-Fehlernummer: " & (num And &HFFFF&)
Else
msg = msg & "(Fehlercode (hexadezimal): " & Hex(num)
End If
' Provider/Server-Fehlernummer
num = .NativeError
If num <> 0 Then
msg = msg & "; NativeError: " & num
msg = msg & " / &H" & Hex(num)
msg = msg & " / " & (num And &HFFFF&)
End If
msg = msg & ")" & vbCrLf
238
6 Connection-Objekt (Verbindungsaufbau)
VORSICHT
VERWEIS
' Hilfe
If .HelpContext <> 0 Then
msg = msg & "Hilfedatei: " & .HelpFile & " ID: " & _
.HelpContext & vbCrLf
End If
End With
Next
Else
' kein Datenbankfehler, sondern normaler VB-Fehler
msg = msg & "VB-Fehlermeldung: " & vbCrLf
msg = msg & Err.Description & vbCrLf
End If
End With
msg = msg + vbCrLf
If IsMissing(starttext) Then
ErrorText = msg
ElseIf IsMissing(endtext) Then
ErrorText = starttext & vbCrLf + vbCrLf & msg
Else
ErrorText = starttext & vbCrLf + vbCrLf & msg & endtext
End If
End Function
Der Umgang mit ADO-Fehlern wird in diesem Buch nicht in einem eigenen
Kapitel beschrieben, sondern innerhalb der jeweiligen Kapitel. Dabei werden
Sie feststellen, daß in vielen Fällen auf eine Auswertung der Error-Eigenschaften verzichtet wird. Einige Querverweise:
Fehlerabsicherung beim Verbindungsaufbau: Seite 207
Fehlerabsicherung bei gebundenen Steuerelementen: Seite 265
Fehlerabsicherung beim Speichern (mit Transaktionen): Seite 472
Fehlerabsicherung beim Löschen (mit Transaktionen): Seite 478
ADO-Fehler können bei jeder ADO-Operation auftreten, selbst dann, wenn Sie
nur eine Eigenschaft lesen. Wenn Sie also glauben, Ihr Programm sei bereits
ausreichend abgesichert, machen Sie folgendes Experiment: starten Sie Ihr Programm, stellen Sie die Verbindung zur Datenbank her und führen einige Operationen durch. Dann trennen Sie die Netzwerkverbindung (Stecker ziehen)
und stellen sie nach ein paar Sekunden wieder her. Dadurch gehen alle Verbindungen zur Datenbank verloren. Es ist im allgemeinen nicht möglich, diesen
Fehler zu beheben (d.h., das Programm muß neu gestartet werden), aber Ihr
Programm sollte selbst in diesem Fall eine plausible Fehlermeldung anzeigen.
6.10 Verbindungs- und Ressourcen-Verwaltung
239
6.10 Verbindungs- und Ressourcen-Verwaltung
Das Herstellen einer neuen Verbindung zu einer Datenbank stellt einen verhältnismäßig aufwendigen Vorgang dar. Aus diesem Grund vermeiden ODBC- als auch OLEDB-Treiber, nicht mehr benötigte Verbindungsobjekte und andere Ressourcen sofort
zu löschen. Statt dessen werden sie noch eine Weile im Speicher gehalten, um sie
möglichst rasch wiederzubeleben, wenn das Programm (oder ein anderes Programm,
das am selben Rechner läuft) eine neue Verbindung benötigt. Diese Vorgehensweise
wird bei ODBC als Connection Pooling und bei OLE DB als Resource Pooling bezeichnet.
Connection Pooling steht seit ODBC 3.0 zur Verfügung. Einige Parameter dieser Strategie – insbesondere die Zeit, wie lange eine nicht mehr benutzte Verbindung im Speicher gehalten werden soll – können im ODBC-Datenquellen-Administrator eingestellt
werden (Start via Systemsteuerung). Die Defaulteinstellung lautet 60 Sekunden.
HINWEIS
Resource Pooling wird von ADO zur Verfügung gestellt (genaugenommen von den
MDAC, also den Microsoft Data Access Components) und funktioniert für alle Datenquellen (nicht nur für ODBC-Verbindungen). Auch wenn der Begriff Resource Pooling
eine umfassendere Funktion andeutet, werden auch hier nur Verbindungsdaten verwaltet.
Es ist nicht möglich, Connection und Resource Pooling gleichzeitig zu verwenden.
Wenn Sie mit ADO 2.1 auf ODBC-Datenquellen zugreifen, wird automatisch
Resource Pooling aktiviert.
VERWEIS
Wenn Sie ADO zur Datenbankprogrammierung verwenden, wird Resource Pooling
automatisch verwendet, d.h., es ist kein besonderer Code erforderlich. Sie sollten allerdings darauf achten, daß während des gesamten Programmablaufs zumindest ein
Connection-Objekt geöffnet ist. Wird dieses geschlossen, verliert das Programm die
Verbindung zur Datenbank. Die Wiederherstellung der Verbindung kann trotz Pooling
aufwendig sein.
In der MSDN-Library (ab Juli 99) finden Sie einen exzellenten Artikel, der
ODBC- und OLE-DB-Pooling beschreibt:
TECHNICAL ARTICLES|DATA ACCESS|POOLING IN THE MS DATA ACCESS COMPONENTS
Herunterladen