Datenbank-Connection nur 1x öffnen

  • VB.NET
  • .NET (FX) 4.5–4.8

Es gibt 13 Antworten in diesem Thema. Der letzte Beitrag () ist von DrZwockel.

    Datenbank-Connection nur 1x öffnen

    Hallo Community,

    ich habe ein Problem, dass etwas drängt.
    Infos:
    Programmiert wird in VB.NET (DotNet 4.6)
    Datenbank: Oracle 11g

    Ich habe ein Projekt, dass aus einer Oracle Datenbank Daten in Richtunng Excel bringt. Hierbei durchläuft der Code die einzelnen Tabellenzeilen und setzt daraus einen SQL-String zusammen, der dann abgefragt und die anschließend ermittelten Werte in die entsprechenden Zellen der Zeile einträgt. So weit, so gut. Dieses Prinzip funktioniert einwandfrei.

    Bei der ersten Testung dieses Moduls kam es auch zu keinen Problemen ... es wurden immer im Durchschnitt 10 Zeilen abgefragt. Jetzt habe ich es mal unter Echtbedingungen laufen lassen und eine Abfrage mit über Tausend Zeilen durchgeführt. Hierbei trat dann der Fehler auf. Irgendwann meldete das Programm:

    VB.NET-Quellcode

    1. Timeout bei im Pool befindlicher Verbindungsanforderung

    Bei der näheren Sicht in den Code stellte ich dann fest, dass beim Durchlaufen der Schleife immer wieder eine Connection zur Datenbank aufgemacht, also ein neues Objekt im Speicher erzeugt wird. Dieser Umstand ist bei wenigen Zeilen unauffällig, bei mehr Zeilen dann aber zuschlägt.

    Ein Kollege erzählte mir, dass es eine Möglichkeit gibt, wonach man einen "default Construktor" erstellen könne, der das Objekt angelgt und dann dauerhaft genutzt werden könne (hoffentlich habe ich ihn da richtig verstanden ?( ). Er programmiert in C#, so dass er mir für VB.NET keine Lösung anbieten konnte. Ich habe daraufhin Tante Google bemüht, bin da aber nicht fünfig geworden. Leider fehlt mir in diesem Bereich die ausreichende Ahnung ... :huh:

    Hat jemand eine Idee, einen Lösungsansatz, der mir weiterhelfen kann ...?

    Vielen Dank für Eure Mühen und Hilfen


    Gruß vom Doc
    Hi,
    wie sieht denn dein bisheriger Code aus? Ein Default Konstruktor ist in VB.Net so ziemlich das gleiche wie in C#. Jede Klasse hat automatisch einen Default Konstruktor wenn kein eigener angegeben wird. Btw. würde ich aber eine DB Connection nicht dauerhaft offen lassen, sondern nur solange, wie daten abgerufen werden.
    Du schreibst das für jede abgerufene Zeile ein DB Object erzeugt wird, das scheint mir so als würdest Du in einer Schleife die Connection öffnen.
    Pack die Öffnung der Connection in ein Using Statement und im Using Statement die Schleife zur Abfrage der Zeilen.
    "Hier könnte Ihre Werbung stehen..."
    Hi MichaHo,

    zunächst vielen Dank für Deine Antwort.

    Ich durchlaufe das Tabellenblatt mittels einer Schleife. In dieser Schleife wird die Connection jedes Mal angesprochen und in der Connection - Klasse dann wieder geöffnet. Hierbei wird ein NEW erzeugt.

    Die Connection sieht wie folgt aus:

    VB.NET-Quellcode

    1. ...
    2. Imports Oracle.ManagedDataAccess.Client
    3. Imports System.Text
    4. Imports FX_ErrLooger
    5. Imports System.Windows.Forms
    6. Public Class FX_Datenbank
    7. Public FX_Datenbank()
    8. ''' <summary>
    9. ''' Verbindungsaufbau zur Oracle-Datenbank
    10. ''' </summary>
    11. ''' <returns></returns>
    12. Public Function Conn2Oracle() As OracleConnection
    13. '### Zusammenbau erfolgt mit einem StringBuilder
    14. '### Connection zusammensetzen und einem String (Conn) übergeben
    15. Dim FX As New OConn_Daten
    16. Dim Conn As New StringBuilder
    17. Dim FXErr As New FX_ErrLogger
    18. '
    19. Conn.Append("DATA SOURCE=")
    20. Conn.Append("(DESCRIPTION=")
    21. Conn.Append("(ADDRESS_LIST=")
    22. Conn.Append("(ADDRESS=")
    23. Conn.Append("(PROTOCOL=TCP)")
    24. Conn.Append("(HOST =" & FX.OHost & ")")
    25. Conn.Append("(PORT =" & FX.OPort & ")")
    26. Conn.Append("))")
    27. Conn.Append("(CONNECT_DATA=")
    28. Conn.Append("(SERVICE_NAME = " & FX.OService & ")")
    29. Conn.Append("));")
    30. Conn.Append("USER ID = " & FX.OUser & ";")
    31. Conn.Append("PASSWORD = " & FX.OPW & ";")
    32. Try
    33. Conn2Oracle = New OracleConnection(Conn.ToString)
    34. If Conn2Oracle.State = ConnectionState.Closed Then
    35. Conn2Oracle.Open()
    36. End If
    37. Catch ex As Exception
    38. MessageBox.Show(ex.Message, "Fehler", MessageBoxButtons.OK, MessageBoxIcon.Error)
    39. End Try
    40. End Function
    41. ...


    während des Durchlaufens der Schleife wird dieser Code immer aufgerufen ...

    VB.NET-Quellcode

    1. ...
    2. '### Datenbankverbindung wird zugewiesen
    3. SQLCMD.Connection = FXC.Conn2Oracle()
    4. '### Command wird auf Text eingestellt
    5. SQLCMD.CommandType = Data.CommandType.Text
    6. '### Command erhält den Stringbefehl
    7. ...
    8. Reader = SQLCMD.ExcecuteReader
    9. ...


    Das mit dem default Construktor habe ich schon einmal gehört, aber wie gesagt, da fehlt mir derzeit noch der Blick für die Ferne ...


    Gruß vom Doc
    Dein Default Konstruktor steht in Zeile #9 deines Codes.
    Wie gesagt, ich würde den Teil für die Connection in ein Using packen und dann innerhalb des Using die Zeilen abfragen. Dann wird die Connection 1 mal geöffnet, die Zeilen abgerufen und wieder geschloßen.
    Als Beispiel hier eine Klasse für eine Verbindung zum SQL-Server:
    Spoiler anzeigen

    VB.NET-Quellcode

    1. Imports System.Data.SqlClient
    2. Public Class DBConnection
    3. Private _ConString As String
    4. Public Sub New(server As String, user As String, pw As String, db As String, timeout As Integer)
    5. _ConString = GenerateConnectionString(server, user, pw, db, timeout)
    6. End Sub
    7. Public Sub InitDBAccess(query As String, param As Dictionary(Of String, Object), dt As DataTable)
    8. Using con As New SqlConnection(_ConString)
    9. Using adap As New SqlDataAdapter(query, con)
    10. FillParams(adap.SelectCommand, param)
    11. dt.Clear()
    12. adap.Fill(dt)
    13. End Using
    14. End Using
    15. End Sub
    16. Private Function GenerateConnectionString(server As String, user As String, pw As String, db As String, timeout As Integer) As String
    17. Dim conStringBuilder As New SqlConnectionStringBuilder()
    18. conStringBuilder.DataSource = server
    19. conStringBuilder.UserID = user
    20. conStringBuilder.Password = pw
    21. conStringBuilder.InitialCatalog = db
    22. conStringBuilder.ConnectTimeout = timeout
    23. Return conStringBuilder.ToString()
    24. End Function
    25. Private Sub FillParams(cmd As SqlCommand, param As Dictionary(Of String, Object))
    26. If param Is Nothing Then
    27. Return
    28. End If
    29. For Each keyvalue In param
    30. cmd.Parameters.AddWithValue(keyvalue.Key, keyvalue.Value)
    31. Next
    32. End Sub
    33. End Class


    in einer weiter Klasse (DAL) werden dann so die Daten abgerufen:
    Spoiler anzeigen

    VB.NET-Quellcode

    1. Private con As DBConnection = New DBConnection("IPDesServers", "Username", "Password", "Datenbank", 300)
    2. Public Sub FillEmployee(dt As DataTable, param As Dictionary(Of String, Object))
    3. Dim query As String = "SELECT * FROM vwMitarbeiter"
    4. con.InitDBAccess(query, param, dt)
    5. End Sub
    "Hier könnte Ihre Werbung stehen..."
    Hallo MichaHo,

    vielen Dank für Deine Mühe.

    Ich habe gerade etwas Schwierigkeiten, den Code mir vor Augen "durchlaufen" zu lassen ... versuche es aber, kurz zu erläutern, wie ich es verstehe ...

    Schwierigkeiten bereitet mir, dass ich die Connection-Werte (Host, PORT, USER, KENNWORT usw.) in der Connection-Klasse habe

    Die eigentiche Connection-Klasse beginnt bei Dir mit :

    VB.NET-Quellcode

    1. Public Class DBConnection
    2. Private _ConString As String
    3. Public Sub New(server As String, user As String, pw As String, db As String, timeout As Integer)
    4. _ConString = GenerateConnectionString(server, user, pw, db, timeout)
    5. End Sub


    Wenn ich es richtig verstehe, dann wird der default-Construktor in der Sub New definiert ... hier übergibst du die erforderlichen Parameter. Diese sind bei mir bereits in der Klasse drin (StringBuilder, s.d.).

    Anschließend kommt der Teil, in dem Du die Parameter zuweist

    VB.NET-Quellcode

    1. Public Sub InitDBAccess(query As String, param As Dictionary(Of String, Object), dt As DataTable) Using con As New SqlConnection(_ConString)
    2. Using adap As New SqlDataAdapter(query, con)
    3. FillParams(adap.SelectCommand, param)
    4. dt.Clear()
    5. adap.Fill(dt)
    6. End Using
    7. End Using
    8. End Sub


    Dann folgt der eigentliche Teil, wo der StringBuilder den Connection-String zusammensetzt ... wobei ich diese Arbeit noch nicht gesehen habe ... sieht interssant aus:

    VB.NET-Quellcode

    1. Private Function GenerateConnectionString(server As String, user As String, pw As String, db As String, timeout As Integer) As String Dim conStringBuilder As New SqlConnectionStringBuilder()
    2. conStringBuilder.DataSource = server
    3. conStringBuilder.UserID = user
    4. conStringBuilder.Password = pw
    5. conStringBuilder.InitialCatalog = db
    6. conStringBuilder.ConnectTimeout = timeout
    7. Return conStringBuilder.ToString()
    8. End Function


    Und dies müsste dann der Teil sein, wo die Parameter liegen, oder sehe ich das falsch?

    VB.NET-Quellcode

    1. Private Sub FillParams(cmd As SqlCommand, param As Dictionary(Of String, Object)) If param Is Nothing Then
    2. Return
    3. End If
    4. For Each keyvalue In param
    5. cmd.Parameters.AddWithValue(keyvalue.Key, keyvalue.Value)
    6. Next
    7. End Sub
    8. End Class


    Ich frage mich abschließend, wo dieser Teil hin kommt ...

    VB.NET-Quellcode

    1. Private con As DBConnection = New DBConnection("IPDesServers", "Username", "Password", "Datenbank", 300)
    2. Public Sub FillEmployee(dt As DataTable, param As Dictionary(Of String, Object))
    3. Dim query As String = "SELECT * FROM vwMitarbeiter"
    4. con.InitDBAccess(query, param, dt)
    5. End Sub



    Gruß vom Doc
    Hi,
    also, die Klasse DBConnection habe ich mir irgendwann mal erstellt weil ich viel mit SQL Datenbanken mache.
    Die Klasse stellt quasi nur die Connection bereit, im Konstruktor wird der Connectionstring erstellt und in der InitDBAccess benötigt.
    Die FillParams ist dazu gedacht, bei SQL Commands Parameter mit zu übergeben wegen SQL-Injektion.
    Die Paramater werden mit null gefüllt, wenn keine Parameter angegeben werden.
    Die Füllung einer Datatable in einem Dataset geschieht dann in einer eignenen Klasse die heisst DAL (Data Access Layer).
    Dort wird der SQL Command angegeben, eventuelle Parameter und an die Funktion InitDBAccess übergeben.
    Die InitDBAccess öffnet die Connection, ruft die Daten ab, schreibt sie in die Tabelle und schließt die Connection wieder.
    Meine Variante setzt voraus, das mit einem typisierten Dataset gearbeitet wird.
    Mit Oracle kenne ich mich nicht so gut aus, aber sollte dort ähnlich funktionieren.
    Vielleicht postest Du mal den kompletten Code, wo du die Connection öffnest und die Daten abrufst.
    "Hier könnte Ihre Werbung stehen..."
    Hallo MichaHo,

    nachfolgend der Teil des Codes, an dem der die Datenbank benötigt wird ... das ganze Vorgeplänkel, wo die Zeilen usw. ermittelt werden, habe ich weggelassen, da sie für den Datenholungsprozess meiner Meinung nach unbedeutend sind ...

    VB.NET-Quellcode

    1. ...
    2. For I = Start to Ende
    3. ....
    4. '### Datenbankverbindung wird zugewiesen
    5. SQLCMD.Connection = FXC.Conn2Oracle()
    6. '### Command wird auf Text eingestellt
    7. SQLCMD.CommandType = Data.CommandType.Text
    8. '### Command erhält den Stringbefehl
    9. '#### jetzt wird die richtige SQL_Abfrage für Daten abgesetzt
    10. SQLCMD.CommandText = ""
    11. SQLCMD.CommandText = RepGespeicherterSQL_String.ToString
    12. '### Reader wird ausgeführt
    13. Reader = SQLCMD.ExecuteReader
    14. If Reader.HasRows Then
    15. While Reader.Read()
    16. Dim ispalte As Integer
    17. For ispalte = 0 To Reader.FieldCount - 1
    18. Reader(ispalte).ToString()
    19. Next
    20. '
    21. '### der erste Spaltenwert ist das Jahr und der Monat (YYYYMM)
    22. JahrMonat = Reader(0).ToString
    23. '### der 2. Spaltenwert ist der eigentliche Spalteninhalt (Wert, Anzahl)
    24. InhaltsWert = Reader(1)
    25. '### Übergabe an die Zuweisungsaufgabe
    26. '### Parameter Tabelle wird nur für die Ermittlung der XXX Daten und deren
    27. '### Berechnung/Formatierung benötigt
    28. InhaltswerteMonatsZuweisung(JahrMonat, DLine, InhaltsWert, Tabelle)
    29. ...
    30. Next i
    31. ...



    In dem Modul

    VB.NET-Quellcode

    1. InhaltswerteMonatsZuweisung
    werden die Werte dann entsprechend Ihres Monatswertes in die Tabelle gebracht.

    Das ganze durchläuft eine For-Schleife. Die Anzahl wird vorher anhand der vorliegenden Zeilen ermittelt ..


    Gruß vom Doc

    Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von „DrZwockel“ ()

    Schonmal versucht, die Zeilen 7 und 9 außerhalb der "For I"-Schleife (Zeile 2) zu platzieren?
    Meiner Meinung müsste dadurch das Problem der tausend geöffneten Connections (erstmal) behoben sein.

    Allerdings ist die Art, wie du die Connection aufbaust, recht fragwürdig, weil ich wette, dass es nirgends Code gibt, der die Verbindung wieder schließt oder Disposed. Eine DbConnection implementiert IDisposable. Schau dir mal in der Hilfe das Schlüsselwort "Using" an und versuche zu verstehen, was es macht und wie das im Zusammenhang mit IDisposable steht. Dann kannst du vielleicht von alleine deinen Code so umstellen, dass es dies berücksichtigt.

    Ich halte es für müßig, dir da jetzt Code vorzukauen, der sich von deinem so stark unterscheiden würde, dass du vermutlich nicht verstehst, warum der so umgestellt wäre und wo der Sinn darin läge, solange du das Konzept von Using und IDisposable noch nicht verinnerlicht hast.
    Weltherrschaft erlangen: 1%
    Ist dein Problem erledigt? -> Dann markiere das Thema bitte entsprechend.
    Waren Beiträge dieser Diskussion dabei hilfreich? -> Dann klick dort jeweils auf den Hilfreich-Button.
    Danke.
    Hallo MichHo,

    ich schreibe sie in eine Exceltabelle ...

    Das I wird schon verwendet. Zum Beispiel beim Auslesen der Zeile ... für die Connection ist sie dann wieder weniger von Bedeutung ... kommt dann wieder beim Eintrag zum Zuge, da die Zeilennummer wieder benötigt wird.

    Die For Schleife regelt in dem Fall, wie oft die Datenbank aufgerufen wird (vereinfacht gesagt).

    Gruß vom Doc
    wie @Arby schon schrieb, gehst Du da falsch ran.
    Ich würde die Daten einmal abrufen und in ein Datatable schreiben, dieses kannst Du dann in ein Excel Blatt zurück schreiben.

    DrZwockel schrieb:

    Die For Schleife regelt in dem Fall, wie oft die Datenbank aufgerufen wird (vereinfacht gesagt).


    ist dann OK, wenn du nach dem abrufen die Connection schließt => Using Statement....
    "Hier könnte Ihre Werbung stehen..."
    ... das mit dem "Ausschütten" des DataTables in die Exceltabelle ist durchaus machbar, leider nicht in diesem Fall, da die Daten an eine ganz bestimmte Stelle in die Tabelle müssen.

    Dies sind durchaus unterschiedliche Zeilen, die sich aus dem durchlaufen der For - Schleife ergeben. Nicht alle Zeile bringen immer Daten, so dass da zwar abgefragt, aber nicht eingetragen wird. Der User kann auch besitmmen, dass er auf 3000 Zeilen nur die Zeilen 375 - 398 abfragen möchte ...

    Ich schaue jetzt mal, dass ich die einzelnen Posts noch einmal durchschaue und eine mögliche Lösung erarbeite ...

    Falls keiner mehr einen Lösungsansatz hat, möchte ich mich für Eure schnelle und offene Hilfe bedanken ...


    Gruß vom Doc
    Ich täte die Daten auch in eine DataTable schreiben und mit dieser dann weiter arbeiten. Ist doch performanter als hunderte von DB Zugriffen zu machen...
    "Gib einem Mann einen Fisch und du ernährst ihn für einen Tag. Lehre einen Mann zu fischen und du ernährst ihn für sein Leben."

    Wie debugge ich richtig? => Debuggen, Fehler finden und beseitigen
    Wie man VisualStudio nutzt? => VisualStudio richtig nutzen
    Hallo MrMo,

    die Idee ist gut, lässt sich meiner Meinung nach aber nicht umsetzen. Es kommen nicht für alle Monate Werte, somit weiß das Programm nicht direkt, wo die Werte eingetragen werden sollen.

    Derzeit durchläuft das Programm die Datensätze und ordnet sie anhand ihrer Monatsnamen und Jahr der entsprechenden Zelle zu. Auch werden nicht alle Zeilen abgefragt, sondern gem. der Auswahl des Nutzers.

    Beim Auskippen der DataTables in das Tabellenblatt wird an einer Zelle begonnen ... ungeachtet, wo es eigentlich hin soll (...).

    Es sei denn, Du hättest da eine entsprechende Lösung, die ich noch nicht gefunden habe ... die würde ich dann natürlilch gerne nehmen ... :)

    Gruß vom Doc