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