Dataset/Datatable doppelten Eintrag verhindern

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

Es gibt 24 Antworten in diesem Thema. Der letzte Beitrag () ist von PapaBeer92.

    Dataset/Datatable doppelten Eintrag verhindern

    Moin,

    ich möchte einige Daten in einem DataSet speichern. Da eine Datenbank-Anbindung (SQL o.ä.) zu aufwendig wäre (Server-Betrieb etc) und so ein DataSet stand-Alone läuft ist das ganz OK.
    Es handelt sich um eine Art Notiz. Ich kann in verschiedenen Feldern Einträge machen (Name, Betreff etc), einige werte werden Automatisch generiert oder per Parameter-Aufruf übergeben, z.B. Zeitpunkt und Rufnummer.

    Nun gebe ich meine Daten in der Anwendung ein, fülle die Textfelder und klicke auf Speichern. Wie schaffe ich es nun, dass er mir, wenn ich noch einmal auf Speichern klicke, keine Doppelten Eintrag erzeugt, sondern den bestehenden Aktualisiert? Mein DataSet hat als Primärschlüssel eine ID. Ich vermute mal, beim erzeugen muss ich mir die Merken und dann beim Speichern angeben. Aber WIE?

    VB.NET-Quellcode

    1. ​Public Class Form1
    2. Private _dataPathCalls As String = "callLog.xml"
    3. Dim dt As DataTable
    4. Private Sub Button1_Click(sender As Object, e As EventArgs) Handles btnSave.Click
    5. Dim callTime As String = lblTime.Text
    6. Dim noteText As String = tbNote.Text
    7. noteText = noteText.Replace("Chr(10)", "{umbr}")
    8. noteText = noteText.Replace("Chr(13)", "{umbr}")
    9. noteText = noteText.Replace(vbCrLf, "{umbr}")
    10. Dim newRow = DataSet1.TblCallLog.NewRow()
    11. newRow(1) = tbCallID.Text
    12. newRow(2) = tbName.Text
    13. newRow(3) = callTime
    14. newRow(4) = tbSubject.Text
    15. newRow(5) = tbKDNr.Text
    16. newRow(6) = tbSN.Text
    17. newRow(7) = cbbTyp.SelectedItem
    18. newRow(8) = tbOrt.Text
    19. newRow(9) = tbTVID.Text
    20. newRow(10) = noteText
    21. dt.Rows.Add(newRow)
    22. End Sub


    Das ist meine bisherige Speichern-Routine. Die Einträge im DataSet werden in einer Listbox Ausgegeben, sodass diese im Nachhinein nochmal betrachtet, und ggf. auch bearbeitet werden können:

    VB.NET-Quellcode

    1. Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
    2. lblVersion.Text = "V " & My.Application.Info.Version.ToString 'Version ausgeben
    3. Me.DataSet1.ReadXml(_dataPathCalls)
    4. dt = DataSet1.TblCallLog
    5. Dim dtCallLog As DataTable = DataSet1.TblCallLog
    6. ListBox1.DataSource = dtCallLog
    7. ListBox1.DisplayMember = "Name"
    8. ListBox1.ValueMember = "ID"
    9. End Sub
    10. Private Sub ListBox1_SelectedIndexChanged(sender As Object, e As EventArgs) Handles ListBox1.SelectedIndexChanged
    11. If Not Me.CanFocus Then Return
    12. tbCallID.Text = CType(ListBox1.SelectedItem, DataRowView).Row.Field(Of String)("CallID")
    13. tbKDNr.Text = CType(ListBox1.SelectedItem, DataRowView).Row.Field(Of String)("KDNr")
    14. tbSN.Text = CType(ListBox1.SelectedItem, DataRowView).Row.Field(Of String)("SNr")
    15. tbOrt.Text = CType(ListBox1.SelectedItem, DataRowView).Row.Field(Of String)("Ort")
    16. tbSubject.Text = CType(ListBox1.SelectedItem, DataRowView).Row.Field(Of String)("Subject")
    17. tbName.Text = CType(ListBox1.SelectedItem, DataRowView).Row.Field(Of String)("Name")
    18. tbTVID.Text = CType(ListBox1.SelectedItem, DataRowView).Row.Field(Of String)("TVID")
    19. tbNote.Text = CType(ListBox1.SelectedItem, DataRowView).Row.Field(Of String)("Note")
    20. lblTime.Text = CType(ListBox1.SelectedItem, DataRowView).Row.Field(Of String)("Time")
    21. End Sub


    Oder gehe ich das ganze komplett Falsch an? Es ist tatsächlich mein erstes mal mit DataSets :thumbsup:

    PapaBeer92 schrieb:

    Mein DataSet hat als Primärschlüssel eine ID. Ich vermute mal, beim erzeugen muss ich mir die Merken und dann beim Speichern angeben.
    Dein tDS hat sicherlich keine ID. Du meinst die DataTables. Aber das Merken/Abfragen derselbigen ist nicht Weg zum Ziel. Sieht so aus, also ob Du gar nicht mit DataBinding arbeitest, sondern alles selber in die Controls schaufelst. Mühsam.

    Das Aktualisieren eines DataTable-Eintrags ist relativ leicht. Man muss nur wissen/festlegen, was als "Ankerpunkt" hergenommen wird. Wenn sich also alle Daten eines Datensatz ändern, bis auf einer und dieser Datensatz dann aktualisiert werden soll und kein neuer angelegt werden soll, welcher Punkt im Datensatz ist der ausschalggebende? CallID (Was immer das auch ist)? Kundennummer? Name?
    Auf jeden Fall würd ich eben in der DataTable suchen, ob es einen Datensatz mit diesem Ankerpunkt schon gibt. Wenn ja, dann aktualisieren, ansonsten einen neuen Datensatz der DataTable hinzufügen.

    Schau mal bei EdRs VVV rein. Da gibt's genügend Infos zum schnellen Einstieg.
    Dieser Beitrag wurde bereits 5 mal editiert, zuletzt von „VaporiZed“, mal wieder aus Grammatikgründen.

    Aufgrund spontaner Selbsteintrübung sind all meine Glaskugeln beim Hersteller. Lasst mich daher bitte nicht den Spekulatiusbackmodus wechseln.

    VaporiZed schrieb:


    Schau mal bei EdRs VVV rein. Da gibt's genügend Infos zum schnellen Einstieg.


    Danke! Das hat schon mal viel geholfen - und es extrem vereinfacht :D Ich habe das Konzept ListBox auch raus geworfen - da ich das DataGridView Konfigurieren kann, passt das. Nun erzeuge ich beim Start des Programms einfach einen neuen Eintrag (wenn eine Rufnummer als Parameter übergeben wird):

    VB.NET-Quellcode

    1. Private Sub GenerateNew(ByVal Rufnummer As String, ByVal Ort As String)
    2. With DataSet1
    3. .TblCallLog.AddTblCallLogRow(Rufnummer, "", callTime, "", "", "", "", Ort, "", "")
    4. .WriteXml(_dataPathCalls)
    5. End With
    6. End Sub


    Problem: Er wählt beim Start immer den obersten, also ersten Eintrag aus - nicht den eben erstellten. Wie verhindere ich das?
    Gefunden:

    VB.NET-Quellcode

    1. Dim cm As CurrencyManager = BindingContext(TblCallLogBindingSource)
    2. cm.Position = cm.Count - 1

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

    Wie hast du denn den CurrencyManager gefunden?
    Also wenn du das kannst, dann müsstest du doch eine Bindingsource noch viel einfacher untersuchen können.

    also probierma:

    VB.NET-Quellcode

    1. TblCallLogBindingSource.Position = TblCallLogBindingSource.Count - 1

    Und immer feste die Dinge untersuchen, mit denen du hantierst.
    Ich verwende dafür vorzugsweise den ObjectBrowser (und paar andere Debugging-Tools): VisualStudio richtig nutzen (Google ist nicht deine Mami)
    Vollzitat des direkten Vorposts an dieser Stelle entfernt ~VaporiZed

    Google (Also Mutti :D) - funktioniert auch ganz gut so. Ein Problem ist aber noch. Ich habe (wie in deinem Video) 2 Tabellen (tblCallLog und tblTyp)
    Im tblCallLog ist das Feld TypID, welches verknüpft ist mit der ID aus tblTyp. Auf der Form ist eine Combobox mit dem Binding an die ID in tblTyp und dem DisplayMember Name.

    Beim Start der Form wird, wie oben beschrieben, über GenerateNew ein neuer Eintrag erzeugt:

    VB.NET-Quellcode

    1. Private Sub GenerateNew(ByVal Rufnummer As String, ByVal Ort As String)
    2. With DataSet1
    3. Dim rwTyp = .tblTyp(0)
    4. .TblCallLog.AddTblCallLogRow(Rufnummer, "", callTime, "", "", "", rwTyp, "", "", "")
    5. .WriteXml(_dataPathCalls)
    6. End With
    7. End Sub


    Nun hatte ich das Problem, dass ich ja die Typen brauche und vorgeben muss/will. Also habe ich einen Sub "First-Start":

    VB.NET-Quellcode

    1. Private Sub FirstStart()
    2. With DataSet1
    3. .Clear()
    4. .tblTyp.AddtblTypRow("Keine")
    5. .tblTyp.AddtblTypRow("Installation")
    6. .tblTyp.AddtblTypRow("Software")
    7. .tblTyp.AddtblTypRow("Datenbank")
    8. .tblTyp.AddtblTypRow("Schulung")
    9. .tblTyp.AddtblTypRow("Dokumentation")
    10. .tblTyp.AddtblTypRow("Hardware")
    11. .tblTyp.AddtblTypRow("Anderes")
    12. .WriteXml(_dataPathCalls)
    13. End With
    14. End Sub


    Wenn ich nun einen Eintrag in der Combobox auswähle kann ich nichts mehr machen. Ich kann zwar Einträge in der Box auswählen, das wars. Nicht mal schließen lässt sich die Form. Geht nur der Kill im Visual Studio. Es kommt aber auch keine Fehlermeldung..

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

    Auf Präfixe wie tbl sollte man verzichten. Auch auf ByVal.

    PapaBeer92 schrieb:

    Wenn ich nun einen Eintrag in der Combobox auswähle kann ich nichts mehr machen.
    Das bedeutet, dass die Daten nicht mehr konsistent sind. Das sind meist IDs, die verloren gegangen sind, aber irgendwo gebraucht werden. Da hatte ich selbst immer wieder Schwierigkeiten, die Ursache zu finden. Wie sieht eigentlich Dein tDS-Designer aus? Mach mal bitte einen Screenshot. Bilder kannst Du forenintern über [+ Erweiterte Antwort] -> _|Dateianhänge|_ -> [Hochladen] posten.
    Dieser Beitrag wurde bereits 5 mal editiert, zuletzt von „VaporiZed“, mal wieder aus Grammatikgründen.

    Aufgrund spontaner Selbsteintrübung sind all meine Glaskugeln beim Hersteller. Lasst mich daher bitte nicht den Spekulatiusbackmodus wechseln.
    Moin,

    danke für die Antwort. Augenscheinlich ist das Problem behoben, es fehlte Tatsächlich der SelectedValue. Allerdings habe ich ein anderes Problem:

    Beim Neustart der Anwendung setzt er den Wert von TypID im ersten Eintrag auf 0 zurück. Ich habe mal Fotos angehängt von der Form, der Datenbank und der Übersicht (ich hab einfach mal 3 Grids reingehauen zum Anschauen der Daten)


    Es ist übrigens egal ob man die Metro-Elemente von Gather nimmt oder die VB-Studio eigenen - hab's ausprobiert :D

    //EDIT: Ich habe das Problem etwas eingrenzen können. Wenn ich die Anwendung starte OHNE einen neuen Eintrag zu generieren überschreibt er mit die TypID der ersten Zeile mit 0.
    Beim Start der Anwendung fange ich die Start-Parameter ab. Ist eine Rufnummer enthalten wird GenerateNew (siehe oben) aufgerufen, ist der Parameter -h wird nichts gemacht außer Anzeigen (kein neuer Eintrag):

    Starte ich die Anwendung mit -h kommt der Fehler.

    Im MyBase.Load Event:

    VB.NET-Quellcode

    1. With DataSet1
    2. If File.Exists(installDir & "\callLog.xml") Then
    3. Try
    4. .ReadXml(_dataPathCalls)
    5. Catch ex As Exception
    6. MsgBox(ex.Message)
    7. End Try
    8. End If
    9. If .TblVorwahl.Count < 1 Then
    10. CreateVorwahlen()
    11. End If
    12. If .tblTyp.Count < 1 Then
    13. CreateTypes()
    14. End If
    15. End With
    16. [...]
    17. If callID.Contains("-") Then
    18. If callID = "-h" Then
    19. callID = ""
    20. Me.Text = Me.Text & " - READONLY"
    21. Dim cm As CurrencyManager = BindingContext(TblCallLogBindingSource)
    22. cm.Position = cm.Count - 1
    23. Else
    24. MsgBox("Ungültiger Parameter!", vbCritical)
    25. callID = ""
    26. Me.Close()
    27. End If
    28. End If


    Heißt eigentlich Lade ich ja nur die XML und springe zum letzten Eintrag im BindingSource...
    Bilder
    • Datenbank.png

      11,43 kB, 780×288, 56 mal angesehen
    • Form.png

      24,93 kB, 704×667, 57 mal angesehen
    • Übersicht.png

      19,22 kB, 704×667, 53 mal angesehen

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

    Deine Problembeschreibung ist unklar.
    Im Dataset gibts mehrere TypIds, welche davon setzt "er" auf 0 ?
    Wo kann ich das in welchem bildchen sehen?

    evtl hilft auch, bei "Readonly" DGV.AllowUsertoAddRows = False zu setzen.

    Deim Code nach zu schliessen, bist du für BindingSources ja nicht zu interessieren, und Objectbrowser ist ja auch nicht dein Ding.
    Dabei hätten die beim Thema Readonly durchaus auch was anzubieten.

    ErfinderDesRades schrieb:


    Im Dataset gibts mehrere TypIds, welche davon setzt "er" auf 0 ?
    Wo kann ich das in welchem bildchen sehen?

    evtl hilft auch, bei "Readonly" DGV.AllowUsertoAddRows = False zu setzen.


    Das DGV steht auf ReadOnly, ich habe aber mal AllowUsersToAddRows auf False gesetzt.
    Er setzt den Eintrag "TypID" im TblCallLog auf 0, aber nur vom ersten Eintrag.
    Entferne ich die ComboBox und wähle den Eintrag Manuell aus (die Grids in der TabPage3 sind editierbar) behält er den Eintrag.
    Die Verknüpfung der Daten sieht du im Bild "Datenbank". Die Tabellen (und die 0) in der "Übersicht". Das Bild Form zeigt halt die Form. Eintrag 0 ist Typ "Keine".

    ich habe hier nochmal die Einstellungen der ComboBox rangehängt - welche ich wie in deinem Video gewählt habe.
    Stelle ich hier alles auf TblCallLogBindingSource - also auch den DataSource, Display-Member und ValueMember gehts - zeigt dann halt nur die TypID an die drin steht, mehr nicht. Logisch.
    Irgendwo liegt der Fehler in der Verknüpfung der Daten - nur wo... Die Verknüpfung der Daten habe ich auch nochmal rangehängt-
    Bilder
    • Einstellungen_CBB.png

      11,3 kB, 418×265, 51 mal angesehen
    • Verknüpfung.png

      23,26 kB, 603×585, 50 mal angesehen

    PapaBeer92 schrieb:

    Das Bild Form zeigt halt die Form. Eintrag 0 ist Typ "Keine".

    ah - 0 = "keine"

    Auf dem Form sehe ich drei DGVs - was zeigen die jeweils an?

    ansonsten: vielleicht hast du Controls auf tabAnruferfassung an dieselbe BindingSource gehängt wie DGV-Spalten von Tabpage3.
    Dann gibts da Wechselwirkungen.
    (Ich mach in zwischen eiglich immer für jedes Tab eines TabControl ein eigenes UserControl, aber dann muss man die Probleme formübergreifenden Databindings lösen.)
    Wenn mehrere Controls dieselbe BindingSource haben und ein Control ändert das BindingSource.Current, wird das bei den anderen auch geändert.
    Dieser Beitrag wurde bereits 5 mal editiert, zuletzt von „VaporiZed“, mal wieder aus Grammatikgründen.

    Aufgrund spontaner Selbsteintrübung sind all meine Glaskugeln beim Hersteller. Lasst mich daher bitte nicht den Spekulatiusbackmodus wechseln.
    Kannst Du mal bitte ein Testprojekt ohne bin-, obj-, .vs- und .git-Ordner und gezippt über [+ Erweiterte Antwort] hochladen? Vielleicht finden wir so das Problem und eine Lösung.
    Dieser Beitrag wurde bereits 5 mal editiert, zuletzt von „VaporiZed“, mal wieder aus Grammatikgründen.

    Aufgrund spontaner Selbsteintrübung sind all meine Glaskugeln beim Hersteller. Lasst mich daher bitte nicht den Spekulatiusbackmodus wechseln.
    <X Option Strict Off und VB6-Namespace. Bevor Du weitermachst, bitte die empfohlenen VS-Einstellungen verwenden.

    Ok, bin mal so tolerant. Was muss ich jetzt machen, um ein Fehlverhalten zu sehen? Wenn ich gar keine Befehlszeilenparameter übergebe, schmiert das Programm ab.
    Dieser Beitrag wurde bereits 5 mal editiert, zuletzt von „VaporiZed“, mal wieder aus Grammatikgründen.

    Aufgrund spontaner Selbsteintrübung sind all meine Glaskugeln beim Hersteller. Lasst mich daher bitte nicht den Spekulatiusbackmodus wechseln.

    VaporiZed schrieb:

    Was muss ich jetzt machen, um ein Fehlverhalten zu sehen? Wenn ich gar keine Befehlszeilenparameter übergebe, schmiert das Programm ab.


    Mit dem Startparameter -h startest du das Programm ohne einen Eintrag zu generieren, also im "History-Modus". Dann ersetzt er vom ersten Eintrag den "Typ", macht daraus also Keine, bzw. TypID 0.
    Startest du das Programm mit einer Rufnummer (+4930123456789) erzeugst du einen neuen Eintrag. Startest du immer wieder mit einer Rufnummer, also einem neuen Eintrag, belässt er die Kategorie (z.B. Datenbank).

    Den verlinkten Beitrag nehme ich mir mal zu herzen - ich komme von VBS (eigentlich sogar von QBasic, anno dazumal)
    In Deinem Form1_Load-EventHandler setzt Du die TblCallLogBindingSource.Position auf TblCallLogBindingSource.Count - 1 (btw: das geht auch einfacher mit TblCallLogBindingSource.MoveLast). Das führt nicht nur dazu, dass der letzte Eintrag aus der Liste gewählt wird, sondern auch, dass sich die ComboBox mit der TypID ändert, da dort drinsteht, dass der "Ausgewählte Wert" (SelectedValue) ebenfalls auf die genannte BS verweist.
    Die Ursache ist anscheinend einfach: ich glaub, dass das Form einfach noch nicht alles geladen hat. Wenn Du den Inhalt des Form1_Load-EventHandlers in den Form1_Shown-EventHandler reinhaust, klappt alles - naja, oder besser ausgedrückt: Der Fehler ist beseitigt.
    Dieser Beitrag wurde bereits 5 mal editiert, zuletzt von „VaporiZed“, mal wieder aus Grammatikgründen.

    Aufgrund spontaner Selbsteintrübung sind all meine Glaskugeln beim Hersteller. Lasst mich daher bitte nicht den Spekulatiusbackmodus wechseln.
    Ich habs grad mal ausprobiert. Ich habe das Setzen der Position aus dem Form1_Load Event raus genommen, dafür

    VB.NET-Quellcode

    1. Private Sub frmMain_Shown(sender As Object, e As EventArgs) Handles MyBase.Shown
    2. TblCallLogBindingSource.MoveLast()
    3. End Sub


    Dennoch ändert er den Typ auf keine. Der knoten muss noch irgendwo anders sitzen :/ Es ist ja auch scheinbar nur ein Anzeigefehler, der Wert bleibt ja in der Spalte TypID vorhanden

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

    Du sollst ja auch ALLES von dem einen Eventhandler in den anderen verschieben, nicht nur die eine Zeile. Den KOMPLETTEN Inhalt des Form_Load-EHs in den Form_Shown_EH verschieben.
    Dieser Beitrag wurde bereits 5 mal editiert, zuletzt von „VaporiZed“, mal wieder aus Grammatikgründen.

    Aufgrund spontaner Selbsteintrübung sind all meine Glaskugeln beim Hersteller. Lasst mich daher bitte nicht den Spekulatiusbackmodus wechseln.