Asynchrones Zurückspeichern eines Datasets in die Datenbank

  • VB.NET
  • .NET 5–6

Es gibt 9 Antworten in diesem Thema. Der letzte Beitrag () ist von ErfinderDesRades.

    Asynchrones Zurückspeichern eines Datasets in die Datenbank

    Servus,

    ich habe 1000 Datensätze, welche ich noch aufbereiten muss und in das DataSet in die jeweiligen Tabellen einspeichern muss. Aus den 1000 Datensätze entstehen dadurch insgesamt 78000 neue einträge in dem Dataset. Diese will ich asnychron zurückspeicher in meine Datenbank(mySQL). Momentan braucht er dafür ca. 18 Minuten um die Datensätze synchron zurückzuspeichern.

    Irgendwo habe ich aber dabei einen Denkfehler. Ich will, das er jede Tabelle asynchron zurückspeichert. Ich habe es mit 3 verschiedenen Varianten Probiert:


    Variante 1:
    Spoiler anzeigen

    ich Rufe für jede geaddete Row einen Sub auf, der den jeweiligen Command erzeugt.

    VB.NET-Quellcode

    1. For Each item As Updater.productData.Unterkategorie4Row In productData.Unterkategorie4.Select("", "", DataViewRowState.Added)
    2. IUnter4(item, "Insert")
    3. Next
    4. Async Sub IUnter4(ds As productData.Unterkategorie4Row, Command As String)
    5. Dim Mysqlcmd As New MySqlCommand
    6. Dim connStr As New MySqlConnectionStringBuilder With {
    7. .Server = Server,
    8. .Database = "productdata",
    9. .UserID = User,
    10. .Password = Password,
    11. .Port = 3306
    12. }
    13. Select Case Command
    14. Case "Insert"
    15. Using conn As New MySqlConnection(connStr.ToString)
    16. Await conn.OpenAsync()
    17. Mysqlcmd.Connection = conn
    18. Mysqlcmd.CommandText = "INSERT INTO `unterkategorie4`(`ID`, `Name`, `Auslesen`, `Unter3ID`, `Pfad`) VALUES " _
    19. & "('" & ds.ID & "','" & ds.Name & "','" & ds.Auslesen & "','" & ds.Unter3ID & "','" & ds.Pfad & "')"
    20. Await Mysqlcmd.ExecuteNonQueryAsync()
    21. End Using
    22. Case "Update"
    23. Using conn As New MySqlConnection(connStr.ToString)
    24. Await conn.OpenAsync()
    25. Mysqlcmd.Connection = conn
    26. Mysqlcmd.CommandText = "UPDATE `unterkategorie4` SET `ID`='" & ds.ID & " ',`Name`='" & ds.Name & "',`Auslesen`='" & ds.Auslesen & "',`Unter3ID`='" & ds.Unter3ID & "',`Pfad`='" & ds.Pfad & "' WHERE `ID` = '" & ds.ID & "'"
    27. Await Mysqlcmd.ExecuteNonQueryAsync()
    28. End Using
    29. Case "Delete"
    30. Using conn As New MySqlConnection(connStr.ToString)
    31. Await conn.OpenAsync()
    32. Mysqlcmd.Connection = conn
    33. Mysqlcmd.CommandText = "DELETE FROM `unterkategorie4` WHERE `ID` = '" & ds.ID & "')"
    34. Await Mysqlcmd.ExecuteNonQueryAsync()
    35. End Using
    36. End Select


    Hier bekomme ich den Fehler, das schon eine besthender OpenReader vorhanden ist.


    Variante 2:
    Spoiler anzeigen
    Ich erzeuge für jede geaddete Row im Vorfeld einen MySqlCommand, diese übergebe ich der Asynchronen Methode:

    VB.NET-Quellcode

    1. For Each item As Woocommerce.Woocommerce.Woocommerce.wp_term_taxonomyRow In Woocom.wp_term_taxonomy.Select("", "", Data.DataViewRowState.Added)
    2. Mysqlcmd.CommandText = "INSERT INTO `wp_term_taxonomy`(
    3. `term_taxonomy_id`,
    4. `term_id`,
    5. `taxonomy`,
    6. `description`,
    7. `parent`,
    8. `count`
    9. )
    10. VALUES(
    11. '" & item.term_taxonomy_id & "',
    12. '" & item.term_id & "',
    13. '" & item.taxonomy & "',
    14. '" & item.description & "',
    15. '" & item.parent & "',
    16. '" & item.count & "'
    17. )"
    18. Iwp_term_taxonomy(Mysqlcmd)
    19. Next
    20. Async Sub Iwp_term_taxonomy(Mysqlcmd As MySqlCommand)
    21. Dim connStr As New MySqlConnectionStringBuilder With {
    22. .Server = Server,
    23. .Database = "wordpress",
    24. .UserID = User,
    25. .Password = Password,
    26. .Port = 3306
    27. }
    28. Using conn As New MySqlConnection(connStr.ToString)
    29. Await conn.OpenAsync()
    30. Mysqlcmd.Connection = conn
    31. Await Mysqlcmd.ExecuteNonQueryAsync()
    32. End Using
    33. End Sub


    Hier erhalte ich auch den Fehler, das es schon ein vorhandener openReader vorhanden ist.


    und Variante 3:
    Spoiler anzeigen

    Ich rufe eine Methode auf, welche direkt während die verbindung zur Datenbank besteht, jede geaddete Row in ein Mysqlcmd umwandelt, welcher dann ausgeführt wird.

    VB.NET-Quellcode

    1. Async Sub Iwp_terms()
    2. Dim Mysqlcmd As New MySqlCommand
    3. Dim connStr As New MySqlConnectionStringBuilder With {
    4. .Server = Server,
    5. .Database = "wordpress",
    6. .UserID = User,
    7. .Password = Password,
    8. .Port = 3306
    9. }
    10. Using conn As New MySqlConnection(connStr.ToString)
    11. Await conn.OpenAsync()
    12. Mysqlcmd.Connection = conn
    13. For Each item As Woocommerce.Woocommerce.Woocommerce.wp_termsRow In Program.Woocom.wp_terms.Select("", "", Data.DataViewRowState.Added)
    14. Mysqlcmd.CommandText = "INSERT INTO `wp_terms`(
    15. `term_id`,
    16. `name`,
    17. `slug`,
    18. `term_group`
    19. )
    20. VALUES(
    21. '" & item.term_id & "',
    22. '" & item.name & "',
    23. '" & item.slug & "',
    24. '" & item.term_group & "'
    25. )"
    26. Await Mysqlcmd.ExecuteNonQueryAsync()
    27. Next
    28. For Each item As Woocommerce.Woocommerce.Woocommerce.wp_termsRow In Program.Woocom.wp_terms.Select("", "", Data.DataViewRowState.ModifiedCurrent)
    29. Mysqlcmd.CommandText &= "UPDATE
    30. `wp_terms`
    31. SET
    32. `name` = '" & item.name & "',
    33. `slug` = '" & item.slug & "',
    34. `term_group` = '" & item.term_group & "'
    35. WHERE
    36. `term_id` = '" & item.term_id & "'"
    37. Await Mysqlcmd.ExecuteNonQueryAsync()
    38. Next
    39. For Each item As Woocommerce.Woocommerce.Woocommerce.wp_termsRow In Program.Woocom.wp_terms.Select("", "", Data.DataViewRowState.Deleted)
    40. Mysqlcmd.CommandText &= "DELETE FROM `wp_terms` WHERE `term_id` = '" & item.term_id & "'"
    41. Await Mysqlcmd.ExecuteNonQueryAsync()
    42. Next
    43. End Using
    44. End Sub


    Hier wird nur ein Datensatz zurückgespeichert.


    Ich denke, ich verstehe hier die Asynchrone Methode nicht so ganz. Wo ist hierbei mein Denkfehler?

    *Topic verschoben*

    Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von „Marcus Gräfe“ ()

    lern doch erstmal, wie man ganz normal ein Dataset in die Db updated.
    Da erzeugt man nicht für jeden Datensatz ein Command, und man verwendet auch DbParameter.
    Am einfachsten ist es mit einem DataAdapter.
    Ungefähr sowas:

    VB.NET-Quellcode

    1. using con=new MySqlConnection(<connectionstring>), cmd=new MySqlCommand("Select foo from bar", con), adp=new MySqlDataAdapter(cmd), bldr=new MySqlCommandBuilder(adp)
    2. adp.Update(<myDataTable>)
    3. End Using
    so ungefähr, such dir die Doku zu den angegebenen Begriffen. Am besten mittm CommandBuilder anfangen, da dürfte die Suche dannn auch ganz brauchbare Beispiele erbringen.

    Deine Vorgehensweise ist jdfs. komplett NoGo, du öffnest da Scheunentore für SqlInjection-Angriffe: gugge Must-Know: Sql-Injection


    Als nächste Stufe sollte man über einen Bulk-Insert nachdenken. Das ist aber eine Spezial-Vorgehensweise, die auch von jedem DbProvider anders abgehandelt wird, und wie MySql das macht weissichnich.
    Erst danach: Also wenn du gelernt hast, mit einem DataAdapter zu arbeiten (und somit SqlInjection-sicher programmieren kannst), und wenn das zu langsam ist, und du dann MySql-Bulk-Insert gelernt hast, und immer noch zu langsam - danach kannst du nochmal nach Async fragen.

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

    Guten Morgen,

    also bei mehreren SQL Statements sammle ich diese in einen StringBuilder und übergeben diese gesammelt an den MySQL Server. Hier verwende ich aber kein Async, weil alles auf einmal übergeben wird. Allerdings sind es bei mir nicht so viele wie bei dir. Ich weiß nicht, ob es begrenzt ist. Aber du könntest es ja zu Testzwecken versuchen.

    Z.B (Hab jetzt nur Copy&Paste gemacht, sollte nur eine Anregung sein)

    VB.NET-Quellcode

    1. Private sqlCommands as StringBuilder = new StringBuilder()
    2. For Each item As Updater.productData.Unterkategorie4Row In productData.Unterkategorie4.Select("", "", DataViewRowState.Added)
    3. Command = ??? ' Muss noch zugewiesen werden
    4. Select Case Command
    5. Case "Insert"
    6. sqlCommands.AppenLine("INSERT INTO `unterkategorie4`(`ID`, `Name`, `Auslesen`, `Unter3ID`, `Pfad`) VALUES " _
    7. & "('" & item.ID & "','" & item.Name & "','" & item.Auslesen & "','" & item.Unter3ID & "','" & item.Pfad & "');")
    8. Case "Update"
    9. sqlCommands.AppenLine("UPDATE `unterkategorie4` SET `ID`='" & item.ID & " ',`Name`='" & item.Name & "',`Auslesen`='" & item.Auslesen & "',`Unter3ID`='" & item.Unter3ID & "',`Pfad`='" & item.Pfad & "' WHERE `ID` = '" & item.ID & "';")
    10. Case "Delete"
    11. sqlCommands.AppendLine("DELETE FROM `unterkategorie4` WHERE `ID` = '" & item.ID & "');")
    12. End Select
    13. Next
    14. Dim connStr As New MySqlConnectionStringBuilder With {
    15. .Server = Server,
    16. .Database = "productdata",
    17. .UserID = User,
    18. .Password = Password,
    19. .Port = 3306
    20. }
    21. Using conn As MySqlConnction = New MySqlConnection(connStr.ToString)
    22. if conn.State <> ConnectionState.Closed then
    23. conn.Close()
    24. end if
    25. conn.Open()
    26. using cmd as MySQLCommand = new MySqlCommand(sqlCommands.ToString(), conn)
    27. cmd.ExecuteNonQuery()
    28. using
    29. End Using


    Den Execute könntest auch Asynchron absetzen...

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

    So ich habe mir jetzt mal den Dataadapter und den BulkLoader bzw. BulkCopy angeschaut.

    Beim Dataadapter komme ich leider nicht weiter, bzgl. der übermittlung der Werte. Er erkennt zwar, das

    VB.NET-Quellcode

    1. Dim ConnStr As New MySqlConnectionStringBuilder With {
    2. .Server = "localhost",
    3. .Database = "wordpress",
    4. .UserID = "root"
    5. }
    6. Using conn As New MySqlConnection(ConnStr.ToString & ";Allow User Variables=true")
    7. conn.Open()
    8. Dim Adt As New MySqlDataAdapter
    9. Adt.InsertCommand = New MySqlCommand("insert into `wp_terms`(`term_id`,`name`, `slug`, `term_group`) values (@term_id,@name,@slug,@term_group)", conn) 'dem Adapter den InsertCommand zuweisen
    10. Adt.InsertCommand.Parameters.Add("@term_id", MySqlDbType.Int32) ' die Datentypen der jeweiligen Parameters zuweisen
    11. Adt.InsertCommand.Parameters.Add("@name", MySqlDbType.String)
    12. Adt.InsertCommand.Parameters.Add("@slug", MySqlDbType.String)
    13. Adt.InsertCommand.Parameters.Add("@term_group", MySqlDbType.Int32)
    14. Adt.Update(Woocommercer.wp_terms) ' Hinzugefügte Daten in die Datenbank schreiben
    15. End Using
    16. Sub AddnewStuff() 'Testweise hinzufügen neuer Daten
    17. For i As Integer = 0 To 10
    18. Woocommercer.wp_terms.Addwp_termsRow("Test" & i, "Test" & i, i)
    19. Next
    20. End Sub


    So, das resultat aus dem Code ist, das er mir 10 leere Spalten in der Datentabelle erstellt, ohne Werte. Sprich, ich muss dem Adapter noch sagen, das der Wert für "@name" in der Spalte "name" aus dem DataSet ist. Nur wie kann ich diese Relation übergeben?

    Den Update und Delete Command werde ich noch hinzufügen, wenn der InsertCommand funktioniert, da ich denke, das diese gleich aufgebaut ist

    @GerhardW danke, so habe ich es ja auch gemacht. Jedoch ist hierbei der Nachteil der Möglichkeit einer Injection, wodurch die Daten aus der Datenbank ausgegeben werden können z.B.

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

    In deinen Zeilen 14-17 fügst du neue Parameter hinzu, schön. Nur fehlt eben der eigentliche Wert der Parameter.

    Infos dazu findest du hier, wenn du ganz noch unten scrollst.
    das habe ich auch schon probiert. Leider trägt er dann 10 mal den selben wert ein:

    VB.NET-Quellcode

    1. Sub Main(args As String())
    2. LoadData()
    3. AddnewStuff()
    4. For Each item As MySQLBULKLOADERTEST.Woocommerce.wp_termsRow In Woocommercer.wp_terms.Select("", "", Data.DataViewRowState.Added)
    5. SaveData(item)
    6. Next
    7. End Sub
    8. Sub SaveData(item As MySQLBULKLOADERTEST.Woocommerce.wp_termsRow)
    9. Dim ConnStr As New MySqlConnectionStringBuilder With {
    10. .Server = "localhost",
    11. .Database = "wordpress",
    12. .UserID = "root"
    13. }
    14. Using conn As New MySqlConnection(ConnStr.ToString & ";Allow User Variables=true;AllowLoadLocalInfile=true")
    15. conn.Open()
    16. Dim Adt As New MySqlDataAdapter()
    17. Adt.InsertCommand = New MySqlCommand("insert into `wp_terms`(`name`, `slug`, `term_group`) values (@name,@slug,@term_group)", conn)
    18. Adt.InsertCommand.Parameters.Add("@name", MySqlDbType.String).Value = item.name
    19. Adt.InsertCommand.Parameters.Add("@slug", MySqlDbType.String).Value = item.slug
    20. Adt.InsertCommand.Parameters.Add("@term_group", MySqlDbType.Int32).Value = item.term_group
    21. Adt.Update(Woocommercer, "wp_terms")
    22. End Using
    23. End Sub
    24. Sub AddnewStuff()
    25. For i As Integer = 0 To 10
    26. Woocommercer.wp_terms.Addwp_termsRow("Test;" & i, "Test;" & i, i)
    27. Next
    28. 'Dim merow = Woocommercer.wp_terms.LastOrDefault(Function(x) x.name = "Allgemein")
    29. 'merow.name = "Test Allgemein"
    30. End Sub


    ich bekomme 10 man dann den Wert Typ:0 zurückgespeichert und nicht wie gewünscht durchnummeriert.
    in post#2 empfahl ich, dass du nach Codebeispielen für CommandBuilder suchst.
    Dein Umgang mittm DataAdapter ist so falsch, dass ich kaum glaube, dass du meine Empfehlung umgesetzt hast.
    Kein Mensch (soweitichweiss) würde einem DataAdapter ein InsertCommand zuweisen - oder fandest du tatsächlich Vorlagen, die solch ein Vorgehen zeigteten?
    hier ein einfaches Bsp. "Insert mit Ratenzahlun(en)" durchnummeriert

    VB.NET-Quellcode

    1. 'command: Insert Ratenzahlungen
    2. 'tabellen Felder:
    3. 'ID = Autoincrement
    4. 'RatenNr = Integer
    5. 'Ratenbetraege = Double
    6. 'RatenDatum = Date
    7. 'RechnungsNr = Integer
    8. Public Sub AddRatenzahlungen(ByVal nZahlungen As Integer, _
    9. ByVal RechnungsBetrag As Double, _
    10. ByVal RatenDatum As Date, _
    11. ByVal RechnungsNr As Integer)
    12. Dim sDB As String = "D:\TestCheck.mdb"
    13. Dim sCon As String = "Provider=Microsoft.Jet.OLEDB.4.0;" & _
    14. "Data Source=" & sDB & ";"
    15. Dim Ratenbetraege As Double = FormatNumber(RechnungsBetrag / nZahlungen, 2)
    16. Dim Con As OleDb.OleDbConnection = New OleDb.OleDbConnection(sCon)
    17. Con.Open()
    18. For iCounter = 1 To nZahlungen Step 1
    19. Dim sql = <sql>
    20. Insert Into tbl_RatenPlan
    21. (RatenNr,Ratenbetraege,RatenDatum,RechnungsNr) Values (?,?,?,?)
    22. </sql>.Value
    23. Dim cmd As New OleDb.OleDbCommand(sql, Con)
    24. cmd.Parameters.Add("RatenNr", OleDbType.Integer).Value = iCounter
    25. cmd.Parameters.AddWithValue("Ratenbetraege", Ratenbetraege)
    26. cmd.Parameters.AddWithValue("RatenDatum", RatenDatum.AddMonths(iCounter - 1))
    27. cmd.Parameters.AddWithValue("RechnungsNr", RechnungsNr)
    28. cmd.ExecuteNonQuery()
    29. Debug.WriteLine(String.Join(",", iCounter, Ratenbetraege, RatenDatum.AddMonths(iCounter - 1), RechnungsNr))
    30. Next
    31. Con.Close()
    32. End Sub
    33. Private Sub Button1_Click(sender As System.Object, e As System.EventArgs) Handles Button2.Click
    34. 'AddRatenzahlungen parameters:
    35. '1) wieviele Raten = 15
    36. '2) RechnungsBetrag = 3253.12
    37. '3) erste Rate fällig = 17.11.2022
    38. '4) RechnungsNr(Fremdschlüssel) = 123
    39. AddRatenzahlungen(15, 3253.12, CDate("17.11.2022"), 123)
    40. End Sub


    verwendet habe ich eine Access DB
    Okay, ich hab es (glaube ich) jetzt richtig gemacht. Zumindest erhalte ich das gewünschte Resultat

    VB.NET-Quellcode

    1. Sub SaveData()
    2. Dim ConnStr As New MySqlConnectionStringBuilder With {
    3. .Server = "localhost",
    4. .Database = "wordpress",
    5. .UserID = "root"
    6. }
    7. Using conn As New MySqlConnection(ConnStr.ToString)
    8. conn.Open()
    9. Dim Dtadapter As New MySqlDataAdapter("Select * from wp_terms", conn)
    10. Dim test As New MySqlCommandBuilder(Dtadapter)
    11. Dtadapter.Update(Woocommercer, "wp_terms")
    12. End Using
    13. End Sub


    Die Daten für die Verbindung sind testweise, diese habe ich zu testzwecken in Klartext rein geschrieben. Kann es wirklich so einfach sein, SQL-Injection sicher zu programmieren?

    trix0 schrieb:

    Kann es wirklich so einfach sein, SQL-Injection sicher zu programmieren?
    Ja.
    Deswegen regichmich ja immer auf, wenns nicht gemacht wird.
    Eiglich reg ich mich auf, dasses überhaupt erlaubt - bzw dasses ühaupt möglich ist, es zu unterlassen.
    Es gibt weltweite Untersuchungen zu IT-Sicherheitslücken und was sie an Schaden anrichten, und SqlInjection rangiert mit Abstand auf Platz 1.
    Und das komplett unnötigerweise.

    Imo müsste man einen Sql-Dialekt auf den Markt bringen, bei dem Werte überhaupt nicht mehr unparametrisiert codierbar sind.



    Aber jetzt guck nochma post#2.
    Ich habe 1) connection, 2) command, 3) dataAdapter und 4) CommandBuilder alle im Using-Block, sodass sie nach verwendung disposed werden.
    bei dir wird nur die connection disposed.

    Dieser Beitrag wurde bereits 2 mal editiert, zuletzt von „ErfinderDesRades“ ()