Abgetrennt von Ressourcen schonen - Datenbanken richtig öffnen und schließen
Hi .Kai,
guter Beitrag, besonders für ASP-Anwendungen zu empfehlen Ich würde Ihn aber gern etwas erweitern.
Und zwar:
Es kann durchaus richtig und wesentlich performanter sein, die Sql(bzw. ODBC)-Connection Objekte nicht vom Garbage-Collector aufräumen zu lassen.
Besonders in Windows-Forms-Applikationen die viele Datenbank Operationen durchführen.
Wenn ich z.B. ein Verwaltungs-Gui oder Ähnliches führ eine bestimmte Datenbank schreibe, kann der Performance-Verlust für das ständig erneute aufbauen der Datenbankverbindungen ziemlich groß werden.
In diesem Fall ist es wahrscheinlich, oder zumindest nach meinen Erfahrungen wesentlich besser, die Connection Objekte als Instanz-Member des Forms oder der bestimmten Klasse im Verbundenen Zustand vorzuhalten, da so bei den unzähligen Datenbankzugriffen die Verbindungszeit wegfällt.
Außerdem ist es so möglich, eventuelle Exceptions, Fehler oder ähnliches direkt in Log-Tables oder ähnliches zu schreiben.
Der Speicherbedarf ist zwar nicht all zu gering, diesen kann man aber getrost in Kauf nehmen.
WICHTIG: Man muß darauf achten, dass einem Connection-Object, gleichzeitig nur eine Reader-Instanz zugeordnet ist. Es kann sehr oft wichtig sein, die Using anweisung so, oder ähnlich zu implemetieren.
// SqlConnection sqlCon ist als Instance-Member deklariert,
// und beim start initialisiert und geöffnet worden.
internal void example()
{
using(SqlCommand cmd = new SqlCommand("select * from table where Id <=" + this.SetID.ToString(), sqlCon))
{
using(SqlDataReader rd = cmd.ExecuteReader())
{
while(rd.Read())
{
//hier der Code
}
}
}
}
// jetzt sollte allerdings im Destructor auf das Abbauen der Verbindung geachtet werden. Sollte durch Garbage-Collection durchgeführt werden, ich achte allerdings immer darauf.
~MyClass(){
this.sqlCon.Close();
}
P.S: keine Gewähr für Rechtschreib-Fehler 😉
// jetzt sollte allerdings im Destructor auf das Abbauen der Verbindung geachtet werden. Sollte durch Garbage-Collection durchgeführt werden, ich achte allerdings immer darauf.
~MyClass(){
this.sqlCon.Close();
}
Hallo,
ich sehe das ein bischen anders,
wer garantire dir das der GC auch wirklich läuft und die Resource freigibt?
lg
L.G.
"Das Problem kennen ist wichtiger, als die Lösung zu finden, denn die genaue Darstellung des Problems führt automatisch zur richtigen Lösung." Albert Einstein
Also im Destruktor ist mit Sicherheit der falsche Platz, aber aus nem anderen Grund. Früher oder später wird der mit Sicherheit aufgerufen, aber die Frage ist viel mehr, ob sqlCon nicht schon vorher vom Garbage Collector behandelt wurde.
Das IDisposable Interface gibt es ja nicht, weil der Garbage Collector sonst die Resourcen nicht freigeben würde. Bei ner ordentlichen Implementation der Klasse sollte das zumindest nicht so sein. IDisposable gibt es um den Zeitpunkt der Freigabe zu kontrollieren.
Also nicht ob, sondern wann.
Die Regel lautet ja: im Destruktor (Finalizer) nicht auf managed Resources zugreifen!
Oder anders: der Garbage Collector garantiert nicht die Reihenfolge in der verlorene Objekte gelöscht werden.
Das bedeutet aber im Umkehrschluss nicht, dass der Garbage Collector keine Resourcen freigibt...
Das Close sollte im Dispose von MyClass kommen, oder in vergleichbaren Methoden, aber nie im Destruktor.
e.f.q.
Aus Falschem folgt Beliebiges
🤔
OK,
richtig IDisposable zu implementieren Funzt natürlich auch gut...
Aber, ist im Destructor wirklich der falsche Platz???
rufe dort auch eigentlich nur immer eine Methode der Klasse auf die die Verbindung schließt, wenn das Objekt noch im Speicher ist. Diese Methode wird dann natürlich auch in Dispose aufgerufen(falls implementiert).
aber warum: "wer garantiert dir das der GC auch wirklich läuft?"
...
Habe mir darüber bis jetzt nur gedanken gemacht, wenn ich in meinem Code unsafe bereiche und/oder DllImports verwendet habe.
Und diesen Satz check ich auch nicht so wirklich, wenn SqlCon als Instanz-Member der Klasse deklariert und initialisiert ist:
"Früher oder später wird der mit Sicherheit aufgerufen, aber die Frage ist viel mehr, ob sqlCon nicht schon vorher vom Garbage Collector behandelt wurde."
????
Die Instanz-Daten sollten doch solange das Objekt meiner Klasse besteht auch im Speicher bleiben.
Hat jemand zu diesem Thema einen Blog oder ne genauere Doku zum GC?
Wann will der denn früher meine Instanz-Member killen? Wie soll denn dann die Objektorientierte Programmierung funktionieren...? Der sollte doch erst meine Instanz-Daten anfassen, wenn diese durch andere Programmteile nicht mehr referenziert werden, oder habe ich da etwas falsch verstanden??? 🤔
Auszug aus "Programmieren mit C#" O'Reilly"
Wie Destruktoren funktionieren
Der GarbageCollector pflegt eine Liste von Objekten, die einen Destrucktor haben. Diese Liste wird jedes mal aktualisiert wenn ein Objekt erzeugt oder zerstört wird.
Wird ein Objekt auf dieser Liste zum ersten Mal eingesammelt, wird es zusammen mit anderen Objekten, die zerstört werden sollen, in eine Que gestellt. Nachdem der Destruktor ausgeführt wurde, sammelt der Garbage Collector das Objekt ein und aktualisiert die Liste"
____________ Ok ... Ich zitiere eine Zeile weiter unten auf der Seite.
".... Der Destrucktor wird vom Garbage Collector aufgerufen.Wenn Sie mit wertvollen unmanaged Ressourcen arbeiten, die Sie schließen und so schnell wie möglich verwerfen möchten, sollten Sie IDisposable implementieren."
Ich möchte doch aber erst die Verbindung schließen, wenn mein Programm ganz kurz vor dem Ende ist, nicht beim Beenden sondern am Ende vom Beenden 😉.
Gibt es noch einen späteren Zeitpunkt als den Destruktor. Habe So noch nie Probleme gehabt, wenn ich Full-Managed-Types verwendet habe...
Lasse mich da gern belehren..... ?(
Original von Franknstein
Nur ein kleiner Punkt:
Exceptions sollten besser direkt am Entstehungsort behandelt werden.
Kann ich nicht so stehen lassen. Exceptions sollten behandelt werden, sobald man sie behandeln kann - und nicht früher!
Zum Finalizer: wenn eine Klasse den Finalizer implementiert, wird er früher oder später auch ausgeführt, soviel steht mal fest. Aber wenn es nicht um unverwaltete Resourcen geht, sollte man auf keinen Fall einen finalizer implementieren.
Um mal "Microsoft .NET Framework-Programmierung" zu stützen:
Also: finalizer nur bei nicht-verwalteten Resourcen, bei denen die Gefahr eines Leaks besteht. Und auch dann sollte man das Dispose-Pattern anwenden um die Möglichkeit zu bieten, den Finalizer zu deaktivieren.
Grüße,
Andre
Danke VIZone,
das ist doch mal eine Aussage...
Mal zum Verständnis, wenn ich den Destrucktor nutze, kann es sein das ein Instanz-Member meiner Klasse schon aufgeräumt ist? Ich dachte es wird erst der Destructor meiner Klasse ausgeführt, und dann alle Instanz-Daten meines Objektes beseitigt...
Thanks, schönen Abend noch.
Jan
Hallo,
Der "Destruktor" ist kein Destruktor im Sinne eines C++-Destruktors. Daher die Verwirrung.
Grüße,
Andre
Um sich nicht dazu verleiten zu lassen Ressourcen zu schließen, weil es einfach zu viel "boilerplate code" ist, sollte man eventuell auch überlegen, ob sich das nicht abstrahieren, bzw. eher "herausziehen" lässt. Sich ständig wiederholende try-finally, bzw. using Blöcke neigen einfach dazu irgendwann vergessen oder sogar bewusst ignoriert zu werden. Folgendes Beispiel sollte ebenfalls zeigen, wie es aussehen könnte.
using System.Data;
using System.Data.Odbc;
using System.Collections.Generic;
// angelehnt an die Klasse JdbcTemplate aus dem Spring Framework
public class AdoTemplate {
public IList<T> Query<T>(string sql, IRowMapper<T> rowMapper) {
IList<T> result = new List<T>();
using (OdbcConnection connection = new OdbcConnection()) {
using (OdbcCommand command = new OdbcCommand(sql, connection)) {
using (OdbcDataReader reader = command.ExecuteReader()) {
while (reader.Read()) {
result.Add(rowMapper.MapRow(reader));
}
}
}
}
return result;
}
}
public interface IRowMapper<T> {
T MapRow(IDataReader reader);
}
// so könnte das dann in der Anwendung aussehen
public class AccountDao {
private AdoTemplate template = /* .. */;
public IList<Account> GetAccounts() {
return template.Query("SELECT * FROM Account", new AccountMapper());
}
private class AccountMapper : IRowMapper<Account> {
public Account MapRow(IDataReader reader) {
return new Account(
(double) reader["Balance"],
(double) reader["CreditInterest"]);
}
}
}
Original von Schakal
Danke VIZone,
das ist doch mal eine Aussage...Mal zum Verständnis, wenn ich den Destrucktor nutze, kann es sein das ein Instanz-Member meiner Klasse schon aufgeräumt ist? Ich dachte es wird erst der Destructor meiner Klasse ausgeführt, und dann alle Instanz-Daten meines Objektes beseitigt...
Thanks, schönen Abend noch.
Jan
Der GC im .NET Framework funktioniert nicht über das Zählen von Referenzen. Es wird einfach nur erkannt: Ist das Objekt zugreifbar oder nicht.
Der GC arbeitet unter anderem ja auch mit Generationen. Eine Generation enthält kurzlebige Objekte, eine andere die langlebigen Objekte. Kurzlebige Objekte werden öfter überprüft und wandern eventuell irgendwann in die Generation der langlebigen Objekte, die dann seltener überprüft werden. Wenn jetzt ein Langlebiges Objekt also ein Member hat, das kurzlebig ist, wird wahrscheinlich erst das Kurzlebige aufgeräumt, bevor der Finalizer des langlebigen aufgerufen wird.
Einfach weil erkannt wird, dass das Kurzlebige vom Programm nicht mehr erreicht werden kann, auch wenn trotzdem noch Referenzen dazu existieren, in einem Objekt das später beseitigt wird.
e.f.q.
Aus Falschem folgt Beliebiges
Ja SUUUPER...
dann hab ich ja in meiner .NET-Vergangenheit viel zu viel Code geschrieben...
und sogar noch unnötige NULL-Prüfungen eingebaut... 😁
👍Auf was man nicht alles durch so einen kleinen Beitrag aufmerksam wird. 👍
8)Wollte doch eigentlich nur schreiben, dass man es bei Datenbank Anwendungen oder Eigenen DB-Connectoren(Nicht SQLServer oder MsIdentityInformationServ. Connectoren) vorziehen sollte eine Datenbankverbindung im geöffneten Zustand als Instanz-Member des Forms oder der Base-Class vorzuhalten. 8)
Danke Jungs... hätte auch weiterhin den Destruktor wie früher benutzt...
Das mit den Destruktoren ist ja richtig, aber bitte nicht eine Connection
geöffnet als Instanzmember aufbewaren.
Das ist Resourcenverschwendung und nicht so gedacht.
Hallo, habe mir gerade das pdf zu using durchgelesen und mal eine Verständnisfrage:
Da steht ja das das Using nach try{}finnaly{} übersetzt wird. Daraufhin habe ich meinen Code folgend geändert.
public void VokabelnLaden()
{
String selectSQL = "Select * FROM Vokabel WHERE AbfrDate<=? AND Stufe<6 ORDER BY Stufe DESC";
using (OleDbConnection Connection = new OleDbConnection())
{
Connection.ConnectionString = @"Provider=Microsoft.Jet.OLEDB.4.0;Data Source=lernstoff.mdb";
OleDbDataAdapter DatenAdapter = new OleDbDataAdapter();
OleDbCommand Command = new OleDbCommand(selectSQL, Connection);
//Command.Parameters.Add("Datum", OleDbType.DBDate);
//Command.Parameters["Datum"].Value = DateTime.Now;
Command.Parameters.AddWithValue("Datum", DateTime.Today);
DatenAdapter.SelectCommand = Command;
DatenTabelle.Clear();
DatenAdapter.Fill(DatenTabelle);
iAktRow = -1;
}
Wenn ich jetzt aber den Connectionpfad zu Datenbank so verändere, das sie nicht gefunden wird, so bekomme ich beim ausführen trotzdem eine Fehlermeldung und sie wird nicht abgefangen. Wie kann den das sein, ich dachte sie würde im try{}-Block stehen?
Try finally sorgt ja nur dafür das bei einer evtl. auftretenen Exception der Code im finally auf jeden Fall noch ausgeführt wird. Dadurch wird keinerlei Ecxeption gefangen. Des musst du weiterhin selber machen.
Baka wa shinanakya naoranai.
Mein XING Profil.
Ah stimmt ja, da habe ich mich selbst ein wenig in die Irre geführt. Da in der PDF eine Textpassage so lautet:
da der Compiler diesen Code intern in einen try-catch-finally Block übersetzt.