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



  • Hi Leute,

    ich habe eine kleine Verständnisfrage.
    Folgender Quellcode:

    class Helper
    {
        private SqlConnection connection=null;
        public SqlConnection Connection
        {
            get { return connection; }
            set { connection = value; }
        }
        public SqlConnection CreateNewConnection()
        {
            return new SqlConnection("context connection=true");
        }
        public void foo()
        {
            using(Connection = CreateNewConnection())
            {
                //do something
            }
        }
    }
    

    So, die Frage ist jetzt, wird dieses SqlConnection-Objekt in der Klasse per Dispose freigegeben, wenn ich aus dem using-Block rausgeh?

    Weitere Frage:
    Ist das ganze überhaupt sinnvoll oder würden 3 aufeinanderfolgende Aufrufe von CreateNewConnection() immer die selbe Verbindung zurückliefern? (Zu dieser Frage komm ich wegen: The SqlConnection draws an open connection from the connection pool if one is available. Otherwise, it establishes a new connection to an instance of SQL Server.)

    Danke für eure Hilfe.



  • Der ConnectionPool wird schnell voll, d.h. du musst die Verbindungen schon schliessen. Allerdings bleiben die Verbindungsdaten nach dem Schliessen im Cache (client- und Serverseitig) bestehen. Wenn du also eine neue Verbindung aufmachst wird diese wesentlich schneller geoeffnet.

    Ich habe in meine DB Klasse das IDisposable Interface eingebunden; allerdings fuehre ich diesen Befehl manuell auf, statt das Objekt unter "using" laufen zu lassen (bei mir waere es sonst unnoetiger Aufwand).



  • Nach meinem .NET Verständnis müsste Dispose aufgerufen werden.
    Deinem Post nach schließe ich, dass das nicht der Fall ist..

    Normalerweise erstellt man im Parameterbereich des using-Statements ein neues Objekt, welches dann bei der schließenden Klammer disposed wird.
    Du speicherst das Objekt per Setter als Member. Was soll .NET denn da disposen? Es gibt ja noch eine Referenz auf das Objekt.

    Versuch's nochmal mit "using (Connection conn = new Connection()) { conn->foo(); }", dann sollte es klappen.



  • dEUs ist einfach noch im C++-Land wo kein GC wartet bis die Freigabe erfolgen kann 🙂 Das mit den Wildpointern ist gar nicht so einfach 😉

    MfG SideWinder



  • @Abbes_:

    Ja, genau darum ja der ganze Aufwand: Um die Verbindungen wieder zu schließen.

    Headhunter schrieb:

    Deinem Post nach schließe ich, dass das nicht der Fall ist..

    Der Schluß ist nicht korrekt. Ich habe es nciht getestet, da ich keine Idee hatte, wie ich testen soll, ob eine Verbindung noch besteht oder nicht.
    Im Nachhinein fällt mir auf, dass ich das ganze auch mit einer eigenen Klasse testen könnte. Das werde ich mal tun.

    Headhunter schrieb:

    Versuch's nochmal mit "using (Connection conn = new Connection()) { conn->foo(); }", dann sollte es klappen.

    Klar, das klappt. Aber das will ich nicht machen.



  • SideWinder schrieb:

    dEUs ist einfach noch im C++-Land wo kein GC wartet bis die Freigabe erfolgen kann 🙂

    Bei so Dingen wie Datenbank-Verbindungen will ich mich nciht auf den GC verlassen. Vor allem nicht, da in der MSDN explizit geschrieben steht, dass man sihc selbst drum kümmern soll 😉



  • Ja, an "Connection" wird am Ende des using-Blocks Dispose() aufgerufen. Du machst in diesem Sinne alles richtig.

    Natürlich mag man sich als Benutzer der Helper-Klasse wundern, warum man per Property eine disposed Connection bekommen kann... das ist recht unintuitiv, vor allem weil Helper selber nicht disposable ist.

    foo() sieht aber gut aus. Wie gesagt ist es nur komisch, wenn man helper.CreateNewConnection() und anschließend helper.foo() macht, dass dann das property ne kaputte Connection liefert. Würde ich so nicht machen.

    EDIT: Und natürlich wird die alte Connection nicht disposed. Wenn du von außen CreateNewConnection() machst und anschließend foo() aufrufst, wird eine neue Connection angelegt, disposed aber die alte wird nicht disposed und wird auch nicht mehr referenziert - der GC macht's irgendwann (nicht gut).



  • @Optimizer:

    In meinem wirklichen Projekt ist die Funktion foo nicht in der Klasse Helper, sondern in einer anderen, die diese Helper-Klasse verwendet.

    Ist es dann immer noch so unintuitiv? Wie würdest du die Problematik lösen?



  • class Database : IDisposable
    {
      private SqlConnection connection;
    
      public Database(string conString)
      {
        this.connection = new SqlConnection [...] //usw.
      }
    
      public bool ExecuteQuery(string query)
      {//... }
    
      public bool ExecuteStoredProcedure(string procName, //params,...)
      {//... }
    
      public Dispose()
      {
         this.connection.Dispose()
      }
    }
    
    classe Helper
    {
       public void foo()
        {
            Database db; //oder woanders...
            using(db = Database("Connection To..."))
            {
                //do something
            }
        }
    }
    

    Vieleicht so. Hoffe der Code funzt so 😉



  • OK, das konntet ihr nicht wissen, aber ich mache das ganze ja überhaupt erst, wegen folgendem Problem:

    Ich habe eine Funktion (z.b. foo), die eine Verbindung benötigt und öffnet. Diese wiederum ruft eine weitere Funktion auf, die ebenfalls eine Verbindung benötigt und öffnet. Damit ich hier jetzt nicht x Verbindungen gleichzeitig offen habe, wollte ich das eben über diesen Member machen und alle greifen auf diesen Member zu (Helper ist bei mir ein Singleton)



  • jo hab ich auch immer, dann kann man später auch locker leicht einen connectionpool draus machen und keinem fällts auf.

    class DBConn
    {
         private static DBConn instance;
         private SQLConnection connection;
    
         private DBConn () { connection = new ...; }
    
         public static SQLConnection GetConn () { if(instance == null) instance = new DBConn(); return instance; }
        // auch mit property denkbar
    
        // eventuell noch eine CreateConn() wenn du das entkoppelt haben willst
        // eventuell noch ein CloseConn() wenn das nötig ist
    }
    

    Aber wieso muss die Methode foo() da drin sein?

    MfG SideWinder



  • 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?


Anmelden zum Antworten