Typisiertes Dataset Update sehr langsam

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

Es gibt 12 Antworten in diesem Thema. Der letzte Beitrag () ist von MaHu1983.

    Typisiertes Dataset Update sehr langsam

    Hallo Zusammen,

    ich habe zu Zeit Ärger mit einen Tabelle aus meinem typisierten Dataset. Das Update via Tableadapter dauert sehr lange. Für einen Insert von ca. 7500 Datensätzen vergehen t > 30 s.
    Die Datenmenge sollte dabei nicht allzu groß sein (<1MB). Ich befinde mich beim Programmieren und Testen natürlich im Hausnetz und habe schon da eine lange Laufzeit. Unsere Techniker sollen die Daten dann allerdings über (teilweise sehr schlechte) Internetverbindungen und VPN in die DB spielen. Dort gibt es dann häufig Timeout Probleme mit der Verbindung an die DB.

    Welche Ansätze kann ich verfolgen, um hier eine bessere Performance zu erreichen? Da ich einfach nur TabellennameTableAdapter.Update(Dataset.Tabellenname) nutze, habe ich keine Idee, wo ich programmtechnisch eingreifen könnte. Beim Debuggen verharrt der Code einfach die ganze Zeit auf dieser Programmzeile, ohne zwischendurch noch andere Teile aus meinem Code zu bearbeiten.

    Ich habe auch schon die automatische Spaltenbreite und PaintEvents von dem Datagridview entfernt, an dem die entsprechende Bindingsource hängt.

    Gerne gebe ich natürlich auch weitere Infos und Details, wenn diese zur Analyse benötigt werden.

    Vielen Dank schonmal für Eure Unterstützung.

    Beste Grüße

    EaranMaleasi schrieb:

    Wie kommen denn die Daten ins Dataset? werden die aus einer Datei eingelesen?


    Die Daten werden von einer Textdatei eingelesen und jede Row mit Dataset.Tabelle.AddTabelleRow(x, y, z) der Datatable/dem Dataset hinzugefügt.

    VB1963 schrieb:

    Versuche zunächst einmal die Bindung zu all deinen Controls während der Aktualisierung der Daten zu deaktivieren und danach wieder zu aktivieren...
    Bindingsource.EnableListChangedEvents=False
    BindingSource.SuspendBindings
    ...


    Das werde ich gleich mal ausprobieren. Antwort folgt...


    Edit: Suspend und ListChangedEnabled(false) haben leider keine Verbesserung gebracht. Nach wie vor werden etwa nur 30 Datensätze pro Sekunde in die DB transportiert.

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

    EaranMaleasi schrieb:

    MaHu1983 schrieb:

    Die Daten werden von einer Textdatei eingelesen und
    Wenn du den Versuch fertig hast (was ich glaube auch nicht viel besserung bringt), dann schau mal in meinen oben geposteten Link. Ist fast das gleiche Problem, gelöst durch einen Insert durch den Provider, und nicht durch das DataSet.


    Danke. Ich arbeite das mal durch.
    Also entweder habe ich mir die falsche Vorlage vorgenommen oder ich kann es nicht umsetzen. Ich habe es zwar mit dem folgenden Teil zum laufen gebracht, habe aber keine Verbesserung in Sachen Performance feststellen können. Wo habe ich den Fehler gemacht bzw. was habe ich noch nicht verstanden?

    VB.NET-Quellcode

    1. Dim strComm = "INSERT INTO " &
    2. ds.tabMaschDatValues.TableName &
    3. "(" & ds.tabMaschDatValues.KomColumn.ColumnName & ", " &
    4. ds.tabMaschDatValues.RepNrColumn.ColumnName & ", " &
    5. ds.tabMaschDatValues.FKRecNrColumn.ColumnName & ", " &
    6. ds.tabMaschDatValues.FKVarIDColumn.ColumnName & ", " &
    7. ds.tabMaschDatValues.VarValueColumn.ColumnName & ", " &
    8. ds.tabMaschDatValues.FKVarClassColumn.ColumnName & ", " &
    9. ds.tabMaschDatValues.FKVarCompartmentColumn.ColumnName & ", " &
    10. ds.tabMaschDatValues.DataTypeColumn.ColumnName & ", " &
    11. ds.tabMaschDatValues.DataImportColumn.ColumnName & ") " &
    12. "VALUES (@" & ds.tabMaschDatValues.KomColumn.ColumnName & ", " &
    13. "@" & ds.tabMaschDatValues.RepNrColumn.ColumnName & ", " &
    14. "@" & ds.tabMaschDatValues.FKRecNrColumn.ColumnName & ", " &
    15. "@" & ds.tabMaschDatValues.FKVarIDColumn.ColumnName & ", " &
    16. "@" & ds.tabMaschDatValues.VarValueColumn.ColumnName & ", " &
    17. "@" & ds.tabMaschDatValues.FKVarClassColumn.ColumnName & ", " &
    18. "@" & ds.tabMaschDatValues.FKVarCompartmentColumn.ColumnName & ", " &
    19. "@" & ds.tabMaschDatValues.DataTypeColumn.ColumnName & ", " &
    20. "@" & ds.tabMaschDatValues.DataImportColumn.ColumnName & ")"
    21. Using myConn = New SqlConnection(My.Settings.CFS1_T_LiveReportConnectionString)
    22. myConn.Open()
    23. Using myTrans = myConn.BeginTransaction
    24. Using myCmd = New SqlCommand(strComm, myConn, myTrans)
    25. myCmd.CommandType = CommandType.Text
    26. Dim rowMD = DirectCast(DirectCast(bsMaschDat.Current, DataRowView).Row, dsLifeReport.tabMaschDatRow)
    27. Dim rowVal = rowMD.GettabMaschDatValuesRows
    28. For Each row In rowVal
    29. myCmd.Parameters.Clear()
    30. myCmd.Parameters.AddWithValue("@" & ds.tabMaschDatValues.KomColumn.ColumnName, row.Kom)
    31. myCmd.Parameters.AddWithValue("@" & ds.tabMaschDatValues.RepNrColumn.ColumnName, row.RepNr)
    32. myCmd.Parameters.AddWithValue("@" & ds.tabMaschDatValues.FKRecNrColumn.ColumnName, row.FKRecNr)
    33. myCmd.Parameters.AddWithValue("@" & ds.tabMaschDatValues.FKVarIDColumn.ColumnName, row.FKVarID)
    34. myCmd.Parameters.AddWithValue("@" & ds.tabMaschDatValues.VarValueColumn.ColumnName, row.VarValue)
    35. myCmd.Parameters.AddWithValue("@" & ds.tabMaschDatValues.FKVarClassColumn.ColumnName, row.FKVarClass)
    36. myCmd.Parameters.AddWithValue("@" & ds.tabMaschDatValues.FKVarCompartmentColumn.ColumnName, row.FKVarCompartment)
    37. myCmd.Parameters.AddWithValue("@" & ds.tabMaschDatValues.DataTypeColumn.ColumnName, row.DataType)
    38. myCmd.Parameters.AddWithValue("@" & ds.tabMaschDatValues.DataImportColumn.ColumnName, row.DataImport)
    39. myCmd.ExecuteNonQuery()
    40. Next
    41. myTrans.Commit()
    42. End Using
    43. End Using
    44. End Using



    'N bissl schmerzt es auch, dass ich mit dem Datengeschubse per Provider so viele Varianten zaubern muss (Update, Insert Delete) und damit den Code furchtbar aufblähe, weil ich das ja für alle Tabellen benötige.
    Ich hab mal den Code von StackOverflow etwas angepasst, sodass du jede x-Belibige Tabelle aus deinem Dataset dort einfügen kannst.
    Ist leider in Ermangelung einer Datenbank ungetestet. Es könnten also evtl. kleine Fehler beim Zusammensetzen des gesamten Strings auftreten.
    Und entschuldige dass es C# ist. Ich kann kein VB Programmieren (wohl aber lesen).

    C#-Quellcode

    1. string ConnectionString = "server=192.168.1xxx";
    2. DataSet ds = new DataSet("YourDataSet");
    3. DataTable xy = ds.Tables["YourTable"];
    4. StringBuilder sCommand = new StringBuilder("INSERT INTO " + xy.TableName + " (");
    5. foreach (DataColumn item in xy.Columns)
    6. {
    7. sCommand.Append(item.ColumnName + ",");
    8. }
    9. sCommand.Remove(sCommand.Length - 1, 1);
    10. sCommand.Append(") VALUES ");
    11. using (SqlConnection mConnection = new SqlConnection(ConnectionString))
    12. using (SqlCommand myComm = mConnection.CreateCommand())
    13. using (SqlTransaction myTrans = mConnection.BeginTransaction())
    14. {
    15. int pi = 0;
    16. foreach (DataRow row in xy.Rows)
    17. {
    18. sCommand.Append("(");
    19. foreach (DataColumn col in xy.Columns)
    20. {
    21. string paramName = "@" + col.ColumnName + pi;
    22. sCommand.Append(paramName + ",");
    23. myComm.Parameters.AddWithValue(paramName, row[col]);
    24. }
    25. sCommand.Remove(sCommand.Length - 1, 1);
    26. sCommand.Append("),(");
    27. pi++;
    28. }
    29. sCommand.Remove(sCommand.Length - 2, 2);
    30. myComm.Transaction = myTrans;
    31. try
    32. {
    33. myComm.ExecuteNonQuery();
    34. myTrans.Commit();
    35. Console.WriteLine("Rows inserted :" + xy.Rows.Count); //evtl. auch mit der Rückgabe von ExecuteNonQuery(); möglich.
    36. }
    37. catch (Exception ex)
    38. {
    39. myTrans.Rollback();
    40. Console.WriteLine(ex);
    41. }
    42. }

    Die Erstellung des Commands lässt sich sicherlich optimieren, doch das sollte schon schneller sein, als das DataSet.

    Dieser Beitrag wurde bereits 4 mal editiert, zuletzt von „EaranMaleasi“ ()

    Kurzes Update nach ein bisschen Bastelei...

    Ich konnte deine Vorlage für meine Zwecke umsetzen und benötige nun für die 7.500 Datensätze ca. 8 - 10 Sekunden. Es ist zwar nicht im Bereich "Augenzwinkern" aber für meine Zwecke in Ordnung.
    Zumal ich nun auch den Fortschritt der Datenübertragung berechnen kann, was mit dem Insert per Dataset nicht möglich war. Insofern geht noch ein bisschen Performance für die Aktualisierung meiner Progressbar flöten. Ein Komfort, den ich allerdings gerne gegen ein bisschen Performance eintausche.

    Ich musste mir noch ein paar Schleifen einbauen. Unter Anderem, weil der SQL Server nur 2100 Parameter pro Übertragung zulässt und bei meiner Anzahl an Datensätzen mit jeweils 9 Spalten knapp 70.000 Parameter zusammen gekommen sind. Ich berechne also erst die Batchlänge und die Anzahl der Batches und Schleife das dann durch. Nicht so schön, aber OK...

    Zur Zeit bin ich noch damit beschäftigt die einzelnen Zeilen nach dem Insert auf "AcceptChanges" zu setzen, so dass der ganze Klumpatsch beim nächsten Klick auf den Button nicht wieder durchläuft und ich muss mir noch eine Seperation für Rowstate Added/Updated/Deleted bauen.

    Folgend poste ich einmal das, was ich aus deiner Vorlage gemacht habe (Vielen Dank dafür)

    VB.NET-Quellcode

    1. Dim iBatchLen = CInt(Math.Round(2000 / ds.Tables.Item(dtName).Columns.Count, 0))
    2. Dim iBatch = Math.Ceiling(ds.Tables.Item(dtName).Rows.Count / iBatchLen)
    3. Dim iRowsCopied = 0
    4. Using mConnection = New SqlConnection(My.Settings.CFS1_T_LiveReportConnectionString)
    5. If mConnection.State = ConnectionState.Closed Then
    6. mConnection.Open()
    7. End If
    8. For b = 1 To iBatch
    9. Dim bExecSQL As Boolean = False
    10. Dim sCommand As System.Text.StringBuilder = New System.Text.StringBuilder("INSERT INTO " & ds.Tables.Item(dtName).TableName & " (")
    11. For Each col As DataColumn In ds.Tables.Item(dtName).Columns
    12. sCommand.Append(col.ColumnName & ", ")
    13. Next
    14. sCommand.Remove(sCommand.Length - 2, 2)
    15. sCommand.Append(") VALUES ")
    16. Using myComm = mConnection.CreateCommand
    17. Using myTrans = mConnection.BeginTransaction
    18. If ds.Tables.Item(dtName).Rows.Count - iRowsCopied < iBatchLen Then
    19. iBatchLen = ds.Tables.Item(dtName).Rows.Count - iRowsCopied
    20. End If
    21. For iRow = 0 To iBatchLen - 1
    22. Dim row = ds.Tables.Item(dtName).Rows.Item(iRow + iRowsCopied)
    23. If row.RowState = DataRowState.Added Then
    24. sCommand.Append("(")
    25. For Each col As DataColumn In ds.Tables.Item(dtName).Columns
    26. Dim paramName = "@" & col.ColumnName & iRow + iRowsCopied
    27. sCommand.Append(paramName & ",")
    28. myComm.Parameters.AddWithValue(paramName, row(col))
    29. Next
    30. sCommand.Remove(sCommand.Length - 1, 1)
    31. sCommand.Append("),")
    32. bExecSQL = True
    33. End If
    34. Next
    35. sCommand.Remove(sCommand.Length - 1, 1)
    36. myComm.Transaction = myTrans
    37. Try
    38. If bExecSQL = True Then
    39. myComm.CommandText = sCommand.ToString
    40. myComm.ExecuteNonQuery()
    41. myTrans.Commit()
    42. End If
    43. iRowsCopied = iRowsCopied + iBatchLen
    44. ToolStripProgressBar1.Value = CInt(iRowsCopied / ds.Tables.Item(dtName).Rows.Count * 100)
    45. Me.Refresh()
    46. Catch ex As Exception
    47. myTrans.Rollback()
    48. Debug.Print(ex.Message)
    49. Return False
    50. End Try
    51. End Using
    52. End Using
    53. Next
    54. End Using
    55. Return True
    56. End Function

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

    MaHu1983 schrieb:

    Ich musste mir noch ein paar Schleifen einbauen. Unter Anderem, weil der SQL Server nur 2100 Parameter pro Übertragung zulässt
    Ich muss da gleich mal einhaken, da mir das zuvor nicht bekannt war.
    Laut diesem SO Post:
    stackoverflow.com/questions/65…er-database-provider-type
    Gelten für verschiedene ADO.NET Provider verschiedene Limits was die Parameter angeht:
    • SQL Server : 2100
    • MySQL : vermutlich 65535
    • Oracle : 64000
    • Postgres : 65535 (Für Funktionen 100)
    • SQLite : 999 (Für Funktionen 100)
    Nur zur Info für alle die Vielleicht auf dieses Parameter Limit stoßen.
    Hast du vielleicht hier noch eine Anregung für mich?

    wie schon erwähnt, muss ich ja jetzt nachsehen, ob die Rows Added/Updated oder sonst was sind. Die jeweilige Gruppe der geänderten Zeilen schreibe ich in eine temporäre DataTable und übergebe diese an meine Insert Funktion. Wenn die Funktion erfolgreich durchgelaufen ist, möchte ich jetzt gerne die Änderungen für alle Zeilen aus dieser Gruppierung übernehmen damit die nicht immer wieder bearbeitet werden. Ich muss jetzt allerdings das AcceptChanges auf der originalen DT laufen lassen, damit er das auch frisst. Dann sind ja aber alle geänderten Zeilen übernommen (also inklusive der Datensätze, die ein Update oder Delete als RowState haben). Eigentlich habe ich mir so schön vorgestellt, dass ich wirklich nur die Zeilen in der temporären DT Accepte um dann noch die Update/Delete Routine durchzulaufen.

    VB.NET-Quellcode

    1. Dim dt As DataTable
    2. bsMaschDatValues.EndEdit()
    3. dt = ds.tabMaschDatValues.GetChanges(DataRowState.Added)
    4. If Not dt Is Nothing Then
    5. If SQLInsert(dt) = True Then ' In der SQLInsert steht dann der Kram von oben drin
    6. dt.AcceptChanges()
    7. 'ds.tabMaschDatValues.AcceptChanges()
    8. End If
    9. End If

    was lernen wir daraus?
    DataTable.GetChanges(DataRowState.Added) ist crap. Weil es erzeugt eine neue DataTable, und das ist nicht die DataTable in deim Dataset.

    Nimm DataTable.Select("", DataRowState.Added), da kriegst du die Added Rows - und zwar die Rows aus deim Dataset - keine Klone.
    Und nach Vollzugs kannste mache DataRow.AcceptChanges.
    Entweder für jede einzeln, oder - wenn du auch andere Änderungen mit der Db synchronisierst - in einem Rutsch: DataTable.AcceptChanges()