Verständnisfrage zu Dispose, using, Referenzen in C# und der Klasse SqlConnection.



  • Das ändert alles nichts daran, dass das Dispose aufgerufen werden muss, wenn ich aus dem using rausgehe.



  • Dann ist das using statement eher ungeeignet. Du kannst dein Singleton Disposable machen und bei Programmende entsorgen.

    Oder wenn du unbedingt keine Verbindung länger offen halten möchtest als nötig, dann überlädst du deine Funktionen alle, die eine SQLConnection brauchen folgendermaßen:

    public void Foo1() {
        using( SQLConnection connection = CreateConnection() ) {
            Foo1(connection);
        }
    }
    
    public void Foo1(SQLConnection connection) {
      // benutze connection
    }
    

    Wann immer du eine Connection schon hast, kannst du das FooX mit Connection-Parameter aufrufen.



  • Nein, dieses Durchreichen der Verbindung will ich ja gerade verhindern. Das war mein erster Ansatz. Aber das wurde mir ziemlich schnell zu dumm.
    Die Sache ist auch, dass ich eine Verbindung eben nicht die gesamte Programmlaufdauer offen haben will, sondern nur eine bestimmte Zeit...

    Ich schreibe im Moment eine Stored Procedure in C#. Das ist die Funktion, in der ich das obige using haben will. Ich habe kein Exception-Handling in diese Funktion eingebaut, da der SQL-Server den Exception-Text als Fehlermeldung anzeigt und das reicht im Grunde immer, dass der User weiß, was er falsch gemacht hat. Und genau deswegen ist es wichtig, dass das Dispose aufgerufen wird, wenn aus dem using rausgegangen wird...



  • ok, neuer Versuch:

    Was haltet ihr davon:

    class DBConnection : IDisposable
        {
            private static SqlConnection conn = null;
            private static int referenceCount = 0;
            private static readonly object padlock = new object();
    
            #region IDisposable Member
    
            public void Dispose()
            {
                lock (padlock)
                {
                    --referenceCount;
                    if (referenceCount == 0)
                    {
                        conn.Dispose();
                        conn = null;
                    }
                }
            }
    
            #endregion
    
            public DBConnection()
            {
                CreateConnection("context connection=true");
            }
            public DBConnection(string connectionString)
            {
                CreateConnection(connectionString);
            }
    
            private void CreateConnection(string connectionString)
            {
                lock (padlock)
                {
                    if (conn == null)
                    {
                        conn = new SqlConnection(connectionString);
                        conn.Open();
                    }
                    ++referenceCount;
                }
            }
    
            public SqlConnection Connection
            {
                get { return DBConnection.conn; }
            }
        }
    

    In meinen Funktionen in denen ich DB-Connections brauch umschließ ich den Block der die Verbindung benötigt einfach mit einem using(DBConnection conn = new DBConnection()).

    Gut?



  • Ich weiss jetzt nicht genau, was du vor hast [...] und v.a. wie;
    aber wenn du mit StoredProcedures arbeitest und die Connection nicht durchreichen willst gibt's Probleme mit denn TransactionStates und evtl noetigen Rollbacks.
    Hast du also Transatktionen, die aufeinaner aufbauen, kommst du um ein durchreichen des ConnectionObjects nicht herum.

    Fuer/Bei Fehlermeldungen laesst du den ReturnCode vom SQL-Server in deiner App ausgeben?
    Ein Exception Handling ist in diesem Fall eigentlich auch nicht verkehrt.

    btw:
    an dieser Stelle passt mal wieder mein Verweis auf den MS PetShop 😃



  • Mit Transaktionen gibt es keinerlei Probleme, siehe Klasse TransactionScope.

    Fuer/Bei Fehlermeldungen laesst du den ReturnCode vom SQL-Server in deiner App ausgeben?

    Verstehe ich nciht. Wenn in meiner Stored Procedure eine Exception auftritt, dann handel ich die nicht. Der SQLServer handelt die dann und zeigt dem Benutzer diese Meldung an.



  • dEUs schrieb:

    Verstehe ich nciht. Wenn in meiner Stored Procedure eine Exception auftritt, dann handel ich die nicht. Der SQLServer handelt die dann und zeigt dem Benutzer diese Meldung an.

    Ehm, ich glaube ich hab' das falsch Verstanden 🙄
    Du reichst die Fehlermeldung des SQL-Servers durch!?

    Mache ich idR auch so, nur an besonders kritischen Stellen handle ich per OUT-Parameter manuell.

    dEUs schrieb:

    Mit Transaktionen gibt es keinerlei Probleme, siehe Klasse TransactionScope.

    Hey, ist neu im Framework 2.0. Ich kannte die noch garnicht - haette mir bei meinem letzten grossen Projekt 'ne Menge Zeit und Aerger sparen koennen...
    Leider konnte ich mich wegen meines Studiums die letzten Monate kaum noch mit .NET beschaeftigen 😞



  • Irgendwie baust du selber einen Connection-Pool, obwohl es das fertig gibt. Anscheinend hast du nicht die Anforderung, dass eine Aufgabe die Verbindung braucht, die eine bestimmte "Vorgänger"-Aufgabe schon verwendet hat. Also würde ich für jede Aufgabe eine neue Connection erstellen (und disposen) und vom Framework poolen lassen.



  • dEUs schrieb:

    ok, neuer Versuch:

    Was haltet ihr davon:

    class DBConnection : IDisposable
        {
            private static SqlConnection conn = null;
            private static int referenceCount = 0;
            private static readonly object padlock = new object();
    
            #region IDisposable Member
    
            public void Dispose()
            {
                lock (padlock)
                {
                    --referenceCount;
                    if (referenceCount == 0)
                    {
                        conn.Dispose();
                        conn = null;
                    }
                }
            }
    
            #endregion
            
            public DBConnection()
            {
                CreateConnection("context connection=true");
            }
            public DBConnection(string connectionString)
            {
                CreateConnection(connectionString);
            }
    
            private void CreateConnection(string connectionString)
            {
                lock (padlock)
                {
                    if (conn == null)
                    {
                        conn = new SqlConnection(connectionString);
                        conn.Open();
                    }
                    ++referenceCount;
                }
            }
    
            public SqlConnection Connection
            {
                get { return DBConnection.conn; }
            }
        }
    

    In meinen Funktionen in denen ich DB-Connections brauch umschließ ich den Block der die Verbindung benötigt einfach mit einem using(DBConnection conn = new DBConnection()).

    Gut?

    Wenn du es dennoch so machen willst, solltest du verhindern, dass man ein dispostes Objekt wieder verwendet. Das hilft, frühzeitig Fehler zu erkennen und der Test kostet minimal (und verglichen mit einem Datenbankzugriff gar nichts).



  • Optimizer schrieb:

    Irgendwie baust du selber einen Connection-Pool, obwohl es das fertig gibt. Anscheinend hast du nicht die Anforderung, dass eine Aufgabe die Verbindung braucht, die eine bestimmte "Vorgänger"-Aufgabe schon verwendet hat. Also würde ich für jede Aufgabe eine neue Connection erstellen (und disposen) und vom Framework poolen lassen.

    Das Problem das ich an der Sache sehe ist eben, dass ich dann zeitweise x Verbindungen GLEICHZEITIG offen habe, d.h. der Connection-Pool greift hier ja überhaupt garnicht.

    Optimizer schrieb:

    Wenn du es dennoch so machen willst, solltest du verhindern, dass man ein dispostes Objekt wieder verwendet. Das hilft, frühzeitig Fehler zu erkennen und der Test kostet minimal (und verglichen mit einem Datenbankzugriff gar nichts).

    Das verstehe ich nicht. Was habe ich übersehen/vergessen?



  • dEUs schrieb:

    Das Problem das ich an der Sache sehe ist eben, dass ich dann zeitweise x Verbindungen GLEICHZEITIG offen habe, d.h. der Connection-Pool greift hier ja überhaupt garnicht.

    Was heißt gleichzeitig? Hast du hundert Threads, die gleichzeitig was committen? Wenn ja, was bringt es, das alles über eine Verbindung zu machen, anstatt über hundert? Wenn nein, sieht es dann so aus:

    using( ... ) {
    }
    using( ... ) {
    }
    using( ... ) {
    }
    using( ... ) {
    }
    using( ... ) {
    }

    Also wird die Verbindung immer wieder dem Pool zugeführt. Wenn du tatsächlich viel gleichzeitig machst, was genau soll deinen selbstgemachten Pool schneller machen als den fertigen?

    Oder hast du sowas:
    using( ... ) {
    using( ... ) {
    using( ... ) {
    }
    }
    }

    Sowas hab ich auch. Ich habe mehrere Service-Klassen, deren Methoden per Remoting aufgerufen werden und sich auch gegenseitig benutzen. Die Connection die von allen Methoden verwendet wird ist ein Member der Service-Klasse und wird vom Connection-Pool verwaltet.
    Die Methoden nehmen keine Connection (auch keine selbstgebaute) und erstellen auch keine. Die Connection wird bei der Konstruktion der Service-Klasse gebaut und lebt genau 5min. Sowas wie Connections gibt es nach außen nicht, nur data sets.

    dEUs schrieb:

    Das verstehe ich nicht. Was habe ich übersehen/vergessen?

    Folgendes ist möglich und sollte nicht sein:

    DBConnection con = new DBConnection();
    con.Dispose();
    irgendwas = con.Connection;  // gar nichts sollte hier mehr gehen
    


  • Optimizer schrieb:

    dEUs schrieb:

    Das Problem das ich an der Sache sehe ist eben, dass ich dann zeitweise x Verbindungen GLEICHZEITIG offen habe, d.h. der Connection-Pool greift hier ja überhaupt garnicht.

    Was heißt gleichzeitig? Hast du hundert Threads, die gleichzeitig was committen? Wenn ja, was bringt es, das alles über eine Verbindung zu machen, anstatt über hundert? Wenn nein, sieht es dann so aus:

    using( ... ) {
    }
    using( ... ) {
    }
    using( ... ) {
    }
    using( ... ) {
    }
    using( ... ) {
    }

    Also wird die Verbindung immer wieder dem Pool zugeführt. Wenn du tatsächlich viel gleichzeitig machst

    Sowas habe ich nciht, nein.

    Optimizer schrieb:

    was genau soll deinen selbstgemachten Pool schneller machen als den fertigen?

    Meine Klasse stellt keinen Pool dar. Meine Klasse stellt sicher, dass alle exakt ein und die selbe Verbindung verwenden.

    Optimizer schrieb:

    Oder hast du sowas:
    using( ... ) {
    using( ... ) {
    using( ... ) {
    }
    }
    }

    Ja. Aber halt nicht in einer Funktion, sondern in einer Hauptfunktion die wiederum kleinere Funktionen aufruft die auch DB-Arbeit leisten.

    Optimizer schrieb:

    Sowas hab ich auch. Ich habe mehrere Service-Klassen, deren Methoden per Remoting aufgerufen werden und sich auch gegenseitig benutzen. Die Connection die von allen Methoden verwendet wird ist ein Member der Service-Klasse und wird vom Connection-Pool verwaltet.
    Die Methoden nehmen keine Connection (auch keine selbstgebaute) und erstellen auch keine. Die Connection wird bei der Konstruktion der Service-Klasse gebaut und lebt genau 5min. Sowas wie Connections gibt es nach außen nicht, nur data sets.

    Und was ist da der Vorteil zu meiner Methode in der die Verbindung nicht 5 Minuten lebt, sondern immer nur so lang wie benötigt?

    Optimizer schrieb:

    dEUs schrieb:

    Das verstehe ich nicht. Was habe ich übersehen/vergessen?

    Folgendes ist möglich und sollte nicht sein:

    DBConnection con = new DBConnection();
    con.Dispose();
    irgendwas = con.Connection;  // gar nichts sollte hier mehr gehen
    

    Ich setze mit Dispose das delete in C++ gleich. D.h. dein Aufruf nach dem Dispose wäre im Grunde einfach falsch. Ist dem nicht so?

    als Disclaimer ;):
    Ich bin neu in der Datenbank-Programmierung...



  • dEUs schrieb:

    Und was ist da der Vorteil zu meiner Methode in der die Verbindung nicht 5 Minuten lebt, sondern immer nur so lang wie benötigt?

    Was heißt schon so lange wie benötigt? In der Praxis treten Datenbankzugriffe oft sehr gehäuft auf und dann wieder eine Stunde lang nichts. Das automatische Schließen nach einer bestimmten Zeit ist eingebaut, ich habe nichts dafür machen müssen und ich habe es deshalb verwendet und mir nicht den Kopf zerbrochen. Ich denke auch, dass es Performance-mäßig recht ideal ist und wir haben generell eher woanders Performance-Probleme.

    Aber jetzt verstehe ich zumindest deinen Anwendungsfall. Wenn du deine Klasse dafür geeignet findest, spricht natürlich nichts dagegen, sie herzunehmen. Das mit dem Pool kam daher, dass ich deinen Anwendungsfall falsch verstanden habe.

    Ich setze mit Dispose das delete in C++ gleich. D.h. dein Aufruf nach dem Dispose wäre im Grunde einfach falsch. Ist dem nicht so?

    Der Aufruf wäre falsch, ja. Was auch falsch ist, davon auszugehen, dass andere oder man selber alles richtig macht. Dein dispostes Objekt zu benutzen führt zu undefiniertem Verhalten, weil je nach Umstände verschiedene Dinge passieren (alles wird geil gemacht, eine Exception kommt, nichts kommt aber nichts wird gemacht, ...). Undefiniertes Verhalten ist grundsätzlich ein Risiko für die Sicherheit und sowas will man in .Net-Klassen nicht haben. Falscher Index, falscher Parameter, falsche Verwendung zur falschen Zeit, alles abfangen. Die Philosophie ist hier gänzlich anders. Man geht nicht davon aus, dass der Benutzer einer Klasse ein Mensch ohne Fehler ist. Das heißt nicht, dass man falsche Benutzung toleriert. Fatal wäre es zu prüfen, ob die Verbindung noch offen ist und wenn nicht, einfach die Operation nicht ausführen. Das würde nicht zu einer Exception führen, aber würde Fehler verbergen. Deshalb sollte man eine hier ObjectDisposedException werfen, um die falsche Benutzung anzuzeigen und nicht zu tolerieren.

    Speziell zum disposen gibt es Guidelines, von denen zwei der wichtigsten du verletzt:
    - erlaube, Dispose() mehrmals aufzurufen
    - erlaube nicht, ein dispostes Objekt zu verwenden

    Andere Bibliotheken, Tools und Programmierer verlassen sich darauf, dass du das Dispose-Pattern korrekt implementierst. Siehe dazu:
    http://msdn2.microsoft.com/en-us/library/fs2xkftw.aspx


Anmelden zum Antworten