Update einer Row im typisierten DataSet

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

Es gibt 29 Antworten in diesem Thema. Der letzte Beitrag () ist von OliverSte.

    Update einer Row im typisierten DataSet

    Hi,

    hab da eine Tabelle im ICaDts und eine im DS04Dts. In DS04Dts werden Daten importiert. Nun schaufel ich die Datensätze von DS04Dts nach ICaDts.
    Eine Childtabelle bekommt neue oder geänderte Datensätze. Es sollen aber natürlich kleine Doubletten entstehen, also prüfe ich vor dem AddMeineTabelleRow() auf Vorhandensein:

    VB.NET-Quellcode

    1. If ICaDts.SD_frau.Where(Function(x) x.Id_ICa = rwICa.Id_ICa).Count = 1 Then
    2. 'rwICa.Id_ICa ist der Key in der Parenttabelle
    3. TextBoxLog.Text = "Die SD_frau Daten sind bereits vorhanden und werden aktualisiert." & vbCrLf & TextBoxLog.Text
    4. Dim rwCheck = ICaDts.SD_frau.Where(Function(x) x.Id_ICa = rwICa.Id_ICa).ElementAt(0)
    5. ICaDts.SD_frau.RemoveSD_frauRow(rwCheck)
    6. rwCheck = Nothing
    7. End If
    8. ICaDts.SD_frau.AddSD_frauRow(rwICa, rwDS04_frau.frau_Vorname, rwDS04_frau.frau_Nachname)


    Das ist jetzt kein Update, sondern Löschen und Anfügen.
    Und es funktioniert nicht, wenn die Childtabelle mehrere Datensätze zum Parentkey enthalten darf, denn Remove löscht ja nur genau eine Row.

    Wie macht man ein Update einer Row?

    Wie löscht man Rows, also quasi "delete from tab where feld=wert"?

    Ich habe da mal etwas ausprobiert:

    VB.NET-Quellcode

    1. Dim rwCheck As ICaDataSet.SD_aufnahmeRow
    2. Dim rwsCheck = ICaDts.SD_aufnahme.Where(Function(x) x.Id_ICa = rwICa.Id_ICa)
    3. For Each rwCheck In rwsCheck
    4. Console.WriteLine("Lösche Aufnahme")
    5. ICaDts.SD_aufnahme.RemoveSD_aufnahmeRow(rwCheck)
    6. Next
    7. rwCheck = Nothing


    Das wirft eine Exception, System.InvalidOperationException: "Die Sammlung wurde geändert; möglicherweise wurde die Enumeration nicht ausgeführt."
    Scheint so, als würde das Remove den Ast absägen, auf dem der Algorithmus sitzt ;)


    Dieser Beitrag wurde bereits 5 mal editiert, zuletzt von „OliverSte“ ()

    OliverSte schrieb:

    Scheint so, als würde das Remove den Ast absägen, auf dem der Algorithmus sitzt
    Ja, genau.
    Und da gibts ein einfaches, bewährtes Rezept:
    Schleifen, die Löschungen im durchschliffenen ausführen, müssen rückwärts laufen.

    1. ZusatzInfo: Mit ForEach gehts nicht, denn das läuft vorwärts.
    2. ZusatzInfo: Mit For i gehts.
    Hallo Oliverste

    Du kannst die Collection so nicht ändern, du musst rückwärts gehen.

    z.b.:

    VB.NET-Quellcode

    1. Dim rwCheck As ICaDataSet.SD_aufnahmeRow
    2. Dim rwsCheck = ICaDts.SD_aufnahme.Where(Function(x) x.Id_ICa = rwICa.Id_ICa)
    3. For i as integer = 0 To rwsCheck.Count-1 Step -1
    4. Console.WriteLine("Lösche Aufnahme")
    5. ICaDts.SD_aufnahme.RemoveSD_aufnahmeRow.Items(i)
    6. Next
    7. rwCheck = Nothing


    Ist nur Auswendig geschrieben. Ich bin davon ausgegangen das rwsCheck ein Collection ist welche von ICollection ableitet und die mit .Item(i) das Item mit dem Index abfragen kannst.
    Muss evtl. ein wenig anpassen.

    Grüße
    Sascha
    If _work = worktype.hard Then Me.Drink(Coffee)
    Seht euch auch meine Tutorialreihe <WPF Lernen/> an oder abonniert meinen YouTube Kanal.

    ## Bitte markiere einen Thread als "Erledigt" wenn deine Frage beantwortet wurde. ##

    @ErfinderDesRadesnullSchönen Dank für den Hinweis, das wusste ich nicht, ahnte es aber bereits ;)
    Ist das denn wirklich die einzige (umständliche) Möglichkeit, ein Update durchzuführen?

    @Nofear23m
    Vielen Dank für das Codebeispiel. Auch wenn es an zwei Stellen falsch ist, hast du mir sehr geholfen!
    Wenn die Schleife rückwärts laufen soll, müssen Start- und Stopwerte umgedreht werden. Step -1 allein reicht dafür nicht.
    Item(i) ist keine Methode der Row, schon gar nicht von Remove(). Es geht mit ElementAt(i) und zwar so:

    VB.NET-Quellcode

    1. checked = False
    2. For Each rwDS04_aufnahme In rwDS04.GetDS04_aufnahmeRows()
    3. ' prüfen: gibt es die Row(s) schon?
    4. If Not checked Then
    5. checked = True
    6. Dim rwsCheck = ICaDts.SD_aufnahme.Where(Function(x) x.Id_ICa = rwICa.Id_ICa)
    7. If (rwsCheck.Count > 0) Then
    8. TextBoxLog.Text = "Aufnahmen für kommNr " + rwICa.kommNr + " sind bereits vorhanden und werden aktualisiert." & vbCrLf & TextBoxLog.Text
    9. For i = (rwsCheck.Count - 1) To 0 Step -1
    10. Console.WriteLine("Lösche Aufnahme " + rwsCheck.ElementAt(i).auf_Nr + " für kommNr " + rwICa.kommNr)
    11. ICaDts.SD_aufnahme.RemoveSD_aufnahmeRow(rwsCheck.ElementAt(i))
    12. Next
    13. End If
    14. rwsCheck = Nothing
    15. End If
    16. ICaDts.SD_aufnahme.AddSD_aufnahmeRow(rwICa, rwDS04_aufnahme.auf_Nr, rwDS04_aufnahme.auf_Art, rwDS04_aufnahme.auf_Seite)
    17. Next
    18. checked = False
    @OliverSte

    ​Stimmt, sorry. War nur so auswendig geschrieben. Aber schau, du hast ja die Funktionsweise verstanden und korrekt umgesetzt.
    Auf das kommt es ja an.

    Schöne Grüße
    Sascha
    If _work = worktype.hard Then Me.Drink(Coffee)
    Seht euch auch meine Tutorialreihe <WPF Lernen/> an oder abonniert meinen YouTube Kanal.

    ## Bitte markiere einen Thread als "Erledigt" wenn deine Frage beantwortet wurde. ##

    @Nofear23m
    Aber bitte, du musst dich für nichts entschuldigen. Ich danke dir!
    Ich habe schon verstanden, dass in diesem Forum statt vorgekautem Code stets kleine Hinweise auf die Lösung, die der Fragesteller dann selbst ausarbeitet, gegeben werden. Das dauert zunächst etwas länger, ist aber nachhaltiger.
    .ElementAt(i) würde ich möglichst vermeiden, das ist eine Linq-Extension, bei der man nicht genau weiß, was sie macht.
    Doch - In diesem Falle weiss mans - und ist schlimm: nämlich wertet bei jedem Aufruf diesen Ausdruck neu aus (Stichwort "Lazy-Evaluation"):

    VB.NET-Quellcode

    1. ICaDts.SD_aufnahme.Where(Function(x) x.Id_ICa = rwICa.Id_ICa)
    und zählt dabei mit, bis i erreicht ist.

    Korrektur:

    VB.NET-Quellcode

    1. Dim rwsCheck = ICaDts.SD_aufnahme.Where(Function(x) x.Id_ICa = rwICa.Id_ICa).ToArray
    2. If (rwsCheck.Length > 0) Then
    3. TextBoxLog.Text = "Aufnahmen für kommNr " + rwICa.kommNr + " sind bereits vorhanden und werden aktualisiert." & vbCrLf & TextBoxLog.Text
    4. For i = (rwsCheck.Length - 1) To 0 Step -1
    5. Console.WriteLine("Lösche Aufnahme " + rwsCheck(i).auf_Nr + " für kommNr " + rwICa.kommNr)
    6. ICaDts.SD_aufnahme.RemoveSD_aufnahmeRow(rwsCheck(i))
    7. Next
    8. End If
    Jetzt wird der Ausdruck nur einmal enumeriert, dann ist rwsCheck ein Array, und schnelleren indizierten Zugriff gibts überhaupt nicht.
    Beachte auch, dass rwsCheck = Nothing üflüssig ist. Die Variable geht anschliessend doch eh aussm Scope, was soll die Zeile denn noch bezwecken?



    OliverSte schrieb:

    ...Ist das denn wirklich die einzige (umständliche) Möglichkeit...
    Soo viel umständlicher als eine ForEach-Schleife ist das doch garnet - ist (in diesem Falle) nicht eine Zeile mehr.



    Übrigens - mit welcher VB-Version arbeitest du?
    Ab 2015 kann man String-Operationen ungleich eleganter abhandeln (Stichwort "String-Interpolation").
    Und es sind hier v.a. die String-Operationen, die die Leserlichkeit des Snippets belasten.



    hihi - aber mit "Lazy-Evaluation" hast du imo eiglich erstmal Lern-/Denk-stoff genug für die nächste(n paar) Stunde(n).

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

    @ErfinderDesRades versteht mich nicht oder will es nicht. Bei ihm weiß man es nicht so genau ;)
    Das "umständlich" bezieht sich auf die Art und Weise, wie ich ein Update mache:
    1. prüfen auf Vorhandensein
    2. ggfs. Row komplett löschen
    3. neue Row adden
    und alles zu Fuß.

    Bei so vielen tollen Funktionen rund um das typisierte DataSet dachte ich, es gäbe vielleicht etwas, was einfacher anzuwenden ist. Im Endeffekt passieren natürlich diese Dinge, klar, aber für mich als Programmierer wäre es einfacher (und sicherer), wenn das Framework das macht.

    Falls dir dazu also noch was einfällt, nur her damit, ich nehme es dankbar an.

    So wie den Hinweis auf Interpolierte Zeichenfolgen in Visual Studio, ich nutze nämlich VS 2017.
    Oder den Tipp mit dem rückläufigen For-Next sowie dem Array, supergeil.
    Danke dafür.

    Die Variable setz ich übrigens auf nothing, weil ich viele viele Tabellen update und rwsCheck immer wieder aufs Neue mit einem anderen Tabellendatentyp deklariert wird. Typisierte Programmierung eben ;)
    Jo, das war Missverständnis - aber du bist dran Schuld! :evil: (Das ist ja immer das wichtigste zu klären ;) )



    Deinen Update verstehe ich garnet.
    In Post#4 sehe ich eine LaufVariable rwDS04_aufnahme, aber anscheinend übersehe ich, wo die eigentlich verwendet wird - ergibt mir keinen Sinn, bislang.
    Also bei Interesse nochmal ansprechen (und dann den richtigen Code zeigen - ich vermute nämlich fast, bei dir läuft ein anderer).



    Und nochmal zur Variable: Glaub mir: du brauchst sie nicht auf Nothing setzen.
    Und wenn du die wie in post#4 auch für andere Tabellen verwendest (also innerhalb eines Blockes lokal mit Dim deklarierst - was guter Stil ist), dann ist das jedesmal eine andere Variable - und braucht nicht auf Nothing gesetzt zu werden, bevor sie aus dem Scope geht.
    Selbst wenn sie nicht aussm Scope ginge, sondern wiederverwendet würde, bräuchtest du sie nicht auf Nothing setzen, denn sobald was neues zugewiesen wird, ist das alte ja ebenso weg.

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

    Hallo @ErfinderDesRades,

    ich bin Schuld? Pffff. gut dass wir das geklärt haben ;)
    Also, in Post #4 wird die Variable rwDS04_aufnahme vom Typ DS04_aufnahmeRow beim Anfügen der neuen Row genutzt ICaDts.SD_aufnahme.AddSD_aufnahmeRow(...)
    Und da siehst du auch mein "Problem". Ich möchte eigentlich nur ein Update einer Row machen, lösche aber bei Vorhanden sein und füge neu an. Das erscheint mir kompliziert. Ach manno, das habe ich doch nun schon x-Mal gesagt.

    Hier nun ein Codeschnipsel der "Update-Routine" für eine Tabelle meines DataSets.

    VB.NET-Quellcode

    1. Dim rwDS04_befund As DS04ausMasc.DS04_befundRow
    2. For Each rwDS04_befund In rwDS04.GetDS04_befundRows()
    3. If Not checked Then
    4. checked = True
    5. Dim rwsCheck = ICaDts.SD_befund.Where(Function(x) x.Id_ICa = rwICa.Id_ICa).ToArray
    6. If (rwsCheck.Length > 0) Then
    7. TextBoxLog.Text = $"{rwsCheck.GetType.ToString.LastRightCut("+")} für {rwICa.kommNr} sind bereits vorhanden und werden aktualisiert.{vbCrLf}{TextBoxLog.Text}"
    8. For i = (rwsCheck.Length - 1) To 0 Step -1
    9. Console.WriteLine($"Lösche {rwsCheck.GetType.ToString.LastRightCut("+")} für kommNr {rwICa.kommNr}")
    10. ICaDts.SD_befund.RemoveSD_befundRow(rwsCheck(i))
    11. Next
    12. End If
    13. rwsCheck = Nothing
    14. End If
    15. ICaDts.SD_befund.AddSD_befundRow(rwICa, rwDS04_befund.bef_Nr, rwDS04_befund.bef_Datum, rwDS04_befund.bef_Supervision, rwDS04_befund.bef_Voraufnahmen, rwDS04_befund.bef_Recall, rwDS04_befund.bef_Ergebnis)
    16. Next
    17. checked = False


    Ach, und ReDim finde ich gar nicht gut. Dann schreib ich lieber einmal set x=nothing.

    ja - hab ich übersehen.
    Aber bei deiner Benamung krieg ich auch Augenkrebs:

    VB.NET-Quellcode

    1. ICaDts.SD_befund.AddSD_befundRow(rwICa, rwDS04_befund.bef_Nr, rwDS04_befund.bef_Datum, rwDS04_befund.bef_Supervision, rwDS04_befund.bef_Voraufnahmen, rwDS04_befund.bef_Recall, rwDS04_befund.bef_Ergebnis)
    Sowas kann nicht dein Ernst sein!

    Also ich hab schon oft Routinen geschrieben, wo eine Row gesucht wurden, und bei Vorhandensein upgedatet, ansonsten neu angelegt wurde.
    Aber in deim Code-Brast trau ich mich das nicht.
    Ich weiß auch nicht sicher, obs das ühaupt ist, was du suchst - an deim Code kann ichs nicht erkennen (Augenkrebs).



    Btw

    VB.NET-Quellcode

    1. Dim rwsCheck = ICaDts.SD_befund.Where(Function(x) x.Id_ICa = rwICa.Id_ICa).ToArray
    ist das nicht dasselbe, als wenn du schriebest:

    VB.NET-Quellcode

    1. Dim rwsCheck = rwICa.GetSD_befundRows()
    ?
    Ne, @ErfinderDesRades, isses nich, denn in meinem Code filter ich ja nach einem Datensatz mit der Bedingung SD_befund.Id_ICa = rwICa.Id_ICa, um zu prüfen, ob es eben diesen "Befund" gibt, um den dann upzudaten, bzw. zu löschen und neu zu schreiben.

    Augenkrebs, hm, ich weiß schon, was du meinst. Die Benamsung ist: Tabellenname "SD_Befund", die Felder heißen "bef_irgendwas". Das ist doch soooo schlimm nun nicht. Klar könnte ich bef_ weglassen, mach ich vielleicht sogar morgen :) Denks dir weg.

    VB.NET-Quellcode

    1. ICaDts.SD_befund.AddSD_befundRow(rwICa, rwDS04_befund.Nr, rwDS04_befund.Datum, rwDS04_befund.Supervision, rwDS04_befund.Voraufnahmen, rwDS04_befund.Recall, rwDS04_befund.Ergebnis)


    Un nu? Besser so?

    OliverSte schrieb:

    Ne, @ErfinderDesRades, isses nich, denn in meinem Code filter ich ja nach einem Datensatz mit der Bedingung SD_befund.Id_ICa = rwICa.Id_ICa, um zu prüfen, ob es eben diesen "Befund" gibt, um den dann upzudaten, bzw. zu löschen und neu zu schreiben.
    also wenn es zw. ICa und Befund eine Relation gibt ICa->Befund, dann geht das von mir vorgeschlagene.
    Dein Filter ergibt ein Array von BefundRows, und das tut mein ParentRow.GetBefundRows() - Aufruf ebenso.
    Also wenn es da die vermutete Relation gibt, dann gibts auch die entsprechende typisierte Methode in deinen typisierten DataRows.



    VB.NET-Quellcode

    1. ICaDts.Befund.AddBefundRow(rwICa, rwBefund.Nr, rwBefund.Datum, rwBefund.Supervision, rwBefund.Voraufnahmen, rwBefund.Recall, rwBefund.Ergebnis)
    so könnte ichs lesen.
    Oder lieber noch

    VB.NET-Quellcode

    1. With rwBefund
    2. ICaDts.Befund.AddBefundRow(rwICa, .Nr, .Datum, .Supervision, .Voraufnahmen, .Recall, .Ergebnis)
    3. end With

    Und ja - da erkennt man, dasses ziemlich unsinnig ist - da scheint ja rwBefund geklont zu werden.
    Aber vlt. auch nicht - ja - es sind 2 verschiedene Befund-Tabellen, ne?
    Und ein Befund soll von einer Tabelle in eine andere kopiert werden, odr?

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

    @VB1963
    Abgesehen davon, dass der Thread gerade ziemlich weit von der ursprünglichen Fragestellung abdriftet, bitte ich einfach darum, mir meine umständliche Prefix Benamsung zu lassen. Es gibt da ziemlich viele Tabellen in meinen DataSets, die vom Inhalt und Bedeutung recht ähnlich sind. So behalte ich den Überblick.

    @ErfinderDesRades
    Lies bitte nochmal ganz oben meine ersten drei Sätze.

    Viele Grüße,
    Oliver
    @ErfinderDesRades
    Ich bitte um Nachsicht. Beim erneuten Lesen ist es mir dann doch noch gedämmert, dass dein Vorschlag ja natürlich dasselbe liefert, also rwIca.GetBefundRows().
    Und wenn das was liefert, solls upgedatet werden.
    Der Code mit with sieht wirklich sehr schön aus. Ich werde die Felder wohl neu benennen.

    @VB1963
    Ich versuche mal, wenigstens einen relevanten Ausschnitt abzubilden. Alternativ könnte ich das DataSet anhängen. Wären das dann einfach alle IcaDataSet.* Dateien aus meiner Solution?
    Hi @VB1963,

    hier ist ein Teil aus dem ICaDataSet. Die Tabelle ICa enthält quasi Stammdaten zum Fall, die Childtabellen zugehörige Daten. Die Tabellen vom "Typ" SD_ enthalten Daten zum Fall, die aus einer bestimmten Datenquelle (XML-Datei) stammen. Daher auch die dämlichen Feldnamen. Könnte ich hier zwar ändern, aber dann würden Sie nicht mehr zu denen in der Importdatei passen, das fände ich doof.
    Die Beziehungen sind 1:n.

    Das DS04ausMasc Dataset im zweiten Bild empfängt den Import der XML-Datei. Direkt danach werden die Daten in das ICaDataSet geschaufelt. Es gibt mehrere Importe, die den Datenstamm sukzessive ergänzen. Es kann eben auch sein, dass Childdaten zu Fällen mehrfach importiert werden, weil sie in der Fremddatenquelle geändert wurden. In meiner Datenbank werden diese Daten nur angezeigt! Dann möchte ich die eigentlich nur updaten. Das war meine Eingangsfrage, wie das Updaten geht.

    Das DatSet heißt so, weil die Schnittstelle den Namen DS04 hat. Die DS04_Befunde (in dem DS04DataSet) heißen so, um sie im Programmcode von den SD_Befunden (im ICaDataSet) abzugrenzen. Ich finde den Präfix an der Stelle nicht so schlimm.

    Dann habe ich eine Lösung erarbeitet und gepostet (für unterschiedliche Childtabellen, etwas verwirrend für Euch). Das funktioniert jetzt zwar ganz wunderbar, aber ich hätte gerne die Sicherheit, dass dies auch der Königsweg ist. Darum geht es.

    Bitte fragen, wenn was unklar ist. Ich danke Euch sehr für die geleistete Hilfe, auch wenn sich das nicht immer so "anhört" ;)
    Bilder
    • ICaDataSet 20170919.jpg

      125,85 kB, 650×751, 179 mal angesehen
    • DS04ausMasc 20170919.jpg

      170,94 kB, 1.248×777, 168 mal angesehen
    also es scheint eine IcaRow zu geben, und dann suchst du alle Ds04BefundRows ab.
    Und wenn die IcaRow ChildRows hat, dann soll was abgeglichen werden.

    Wenn das stimmt, dann ist schoma die Logik falsch aufgebaut - da wären ja nur eine Menge IcaBefunRows gegen eine Menge Ds04BefundRows abzugleichen.
    Und wenn die IcaBefundRow-Menge leer ist, ist schoma garnix zu tun.
    Dann ist ja falsch, die Ds04-Befunde durchzuloopen, und die IcaBefunde jede Runde neu zu ermitteln.

    Oder anders ausgedrückt: die Ds04-Befunde sind in die IcaBefunde zu mergen.
    Beim mergen sind verschiedene Regeln möglich.
    Bei dir siehts so aus, dass du alle aufgelisteten IcaBefunde entfernst, und die Ds04-Befunde stattdessen einspielst.
    Kann man machen... (das ist bremisch, - die Bremer sind ja sehr diplomatisch ;) . Wirklich nachdenklich sollte es einen stimmen, wenn ein Bremer sagt: "Kann man auch machen..." :D )

    jdfs Weiter denke ich erstmal nochnicht, weil zu 50% sehe ich mich schon wieder auffm falschen Dampfer.

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