Viele Programme und insbesondere Asp.NET Seiten verwenden Datenbanken, um Daten zu speichern und zu verwalten. Grundsätzlich sollte jeder Entwickler sorgsam mit Verbindungen zu solchen Ressourcen umgehen, d.h. geöffnete Verbindungen müssen immer geschlossen werden sobald diese nicht mehr benötigt werden. Allerdings ist oft genau das Gegenteil der Fall. Dieser Artikel soll kurz aufzeigen, welche Fehler am häufigsten gemacht werden, und wie man mit einem kleinen Trick diese umgehen kann.
Verbindungen zu solchen Ressourcen wie Datenbanken herzustellen benötigt Zeit. Deshalb arbeitet das .NET Framework mit dem sog. Connection-Pooling. Der Connection-Pool hält eine gewissen Anzahl von möglichen Verbindungen vor und teilt diese den anfragenden Teilen des Programms zu. Dabei wird natürlich darauf geachtet, daß Verbindungen nicht mehr geöffnet sind, bevor sie neu zugeteilt werden. Sobald alle Verbindungen aufgebraucht sind wird eine entsprechende Exception ausgegeben.
Unter normalen Umständen reicht die Anzahl der möglichen Verbindungen aus. Sollte es doch mal zu einem Engpaß kommen ist oft die Unachtsamkeit des Entwicklers schuld, der eine Verbindung nicht geschlossen hat. Aber selbst wenn dieser alles richtig gemacht hat, können offene Verbindungen zurückbleiben.
Häufig werden Datenbanken nach folgendem Schema geöffnet und geschlossen:
string sql = "SELECT * FROM Tabelle1"; OdbcConnection sqlConn = new OdbcConnection(); OdbcCommand sqlCmd = new OdbcCommand(sql, sqlConn); sqlConn.Open(); OdbcDataReader sqlReader = sqlCmd.ExecuteReader(); while (sqlReader.Read()) { Console.WriteLine(sqlReader["ID"]); } sqlReader.Close(); sqlConn.Close();
Dieser Code erfüllt seinen Zweck: Verbindung zur Datenbank wird geöffnet, Daten werden gelesen, Verbindung zur Datenbank wird geschlossen. Was aber passiert mit der Verbindung, wenn während des Lesens der Daten eine Exception auftritt? Sie bleibt geöffnet, zumindest solange bis der GarbageCollector die Verbindung irgendwann schließt. Tritt dieser Fehler nun öfters auf, verringert sich die Anzahl der möglichen Verbindungen im Connection-Pool, bis eine Exception ausgegeben wird.
Häufig wird jetzt ein Try-Catch-Finally Konstrukt in die Methode eingebaut:
string sql = "SELECT * FROM Tabelle1"; OdbcConnection sqlConn = new OdbcConnection(); OdbcCommand sqlCmd = new OdbcCommand(sql, sqlConn); try { sqlConn.Open(); OdbcDataReader sqlReader = sqlCmd.ExecuteReader(); while (sqlReader.Read()) { int id = Convert.ToInt32(sqlReader["Name"]); } sqlReader.Close(); } finally { sqlConn.Close(); }
Der Code wirkt jedoch unübersichtlich und oft wird im finally Abschnitt trotzdem das Schließen der Verbindung vergessen.
Übersichtlicher Code, der trotzdem im Falle eines Fehler die Verbindung schließt, erhält man mit sog. „scopes“. Ein scope ist nichts anderes als ein in geschweifte Klammern eingeschlossener Codeabschnitt. Das Objekt, welches die Verbindung zur Datenbank aufbaut, ist also nur innerhalb dieses scopes gültig. Im Code würde das folgendermaßen aussehen:
using (OdbcConnection sqlConn = new OdbcConnection() { OdbcCommand sqlCmd = new OdbcCommand(sql, sqlConn); sqlConn.Open(); OdbcDataReader sqlReader = sqlCmd.ExecuteReader(); while (sqlReader.Read()) { Console.WriteLine(sqlReader["ID"]); } sqlReader.Close(); sqlConn.Close(); }
Diese Vorgehensweise hat mehrere Vorteile. Zum einen wird der Zugriff auf eine bereits geschlossene Verbindung verhindert, da das entsprechende Objekt außerhalb des scopes nicht vorhanden ist. Zum anderen wird beim Auftreten eines Fehler in jedem Fall die Verbindung zur Datenbank geschlossen, da der Compiler diesen Code intern in einen try-catch-finally Block übersetzt. Somit ist es auch möglich das manuelle Schließen der Datenbank ganz wegzulassen. Der Übersicht halber wird es in diesem Beispiel aber trotzdem verwendet.
Mit Hilfe des Tool ildasm.exe kann man dies anhand der vom Compiler erzeugten Metadaten überprüfen:

Wichtig ist die Zeile:
IL_005f:callvirt instance void[mscorlib]System.IDisposable::Dispose()
Dieser Befehl gibt die Anweisung, daß alle offenen Ressourcen geschlossen werden sollen.
Mit Hilfe dieser kleinen Änderung am Code kann man nun davon ausgehen, daß die Verbindung zur Datenbank immer geschlossen wird. Außerdem fängt man weitere mögliche Fehler schon beim kompilieren ab.
Guter Hinweis mit den scopes !