Verständnisfrage EF6 Tracking DataBinding

  • C#

Es gibt 20 Antworten in diesem Thema. Der letzte Beitrag () ist von Nofear23m.

    Verständnisfrage EF6 Tracking DataBinding

    Hallo,

    ich möchte von einer Datenbank (Tabelle) Daten auslesen und einem DatGrid als Datenquelle übergeben. Der Nutzer soll dann im Grid bestehende Datensätze modifizieren und auch neue Datensätze (Zeilen) anlegen dürfen.

    Wie ich es bisher umgesetzt habe, werden die Daten korrekt gelesen und ins Grid eingetragen. Wenn ich jetzt aber ein SaveChanges() absetze werden in der DB nur die bisher vorhandenen Datensätze modifiziert, die neuen aber nicht eingefügt. Nach langem Rumprobieren kriege ich es jetzt hin. Nur muss ich für die neuen Zeilen des Grid das Entity-Status explizit per Hand auf Added setzen. Ich dachte, das geht automatisch und man muss nicht jedes einzelne Flag per Hand setzen.

    Ich gehe daher mal davon aus, dass ich grundsätzlich was falsch mache. Aber was? Oder wie mache ich es, dass die im DatGrid neu hinzugefügten Zeilen dann auch in der DB landen ohne dass ich zusätzlich Hand anlegen muss?


    folgender rudimentärer und vereinfachter Code wird verwendet um die Daten zu laden und ans Grid zu binden:

    C#-Quellcode

    1. ​db = new MeinDBContext();
    2. var Daten = db.Entities.Where(xx=>xx.Bedingung==true).ToList();
    3. dataGrid.Itemssource = Daten;
    4. ...


    C#-Quellcode

    1. private void DataGrid_InitializingNewItem(object sender, InitializingNewItemEventArgs e) {
    2. Entity zug = e.NewItem as Entity;
    3. zug.Ordinal = (dataGrid.Items.IndexOf(zug) + 1) * 10;
    4. zug.PlanID = PlanID;
    5. db.Entry(zug).State = EntityState.Added;
    6. }


    C#-Quellcode

    1. ...
    2. db.SaveChanges();
    3. db.Dispose();



    Ich habe das Ganze auch nicht nur mit List sondern auch mit BindingList probiert. Nur da kann Where nicht verwendet werden und komischerweise lässt das DataGrid dann auch keine neuen Zeilen zu.


    Gruß

    MQ
    Datagrid? was ist das denn für ein Control? Ist das wirklich das von 2003?
    Das gibts tatsächlich immer noch, aber seit 2005 benutzt das niemand mehr. Sondern DatagridView.

    Aber zur Frage:
    Daten ist eine List<T>, die du mit iwas aussm EntityContext befüllt hast.
    Woher soll der EntityContext denn wissen, ob du späterhin dieser Liste noch iwas anneres hinzufügst?

    Ja, das sind so die Segnungen vons dolle neue EF - da muss man iwie hinterher-hühnern, dass die Änderungsverfolgung auch richtig funzt. Iwas mit Datensätze attachen und Kram - ich kenn mich damit nicht aus.
    Ich fummel lieber mit dem völlig outdateten, und total un-hippen typisierten Dataset rum, wo die Änderungsverfolgung einfach nur macht, was man denken sollte, was eine Änderungsverfolgung macht.
    Hallo

    Du musst es der Context-Instanz so hinzufügen das der ChangeTracker es mitbekommt.
    db.DbSetName.Add(dieNeueEntität)
    und dann erst SaveChanges().

    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. ##

    ErfinderDesRades schrieb:

    Aber zur Frage:
    Daten ist eine List<T>, die du mit iwas aussm EntityContext befüllt hast.
    Woher soll der EntityContext denn wissen, ob du späterhin dieser Liste noch iwas anneres hinzufügst?

    Ich füge der List garnix hinzu sondern dem DataGrid eine neue Zeile. Diese Zeile findet sich danach auch in der Liste, die mit dem Context noch verbunden ist. UPDATEs werden ja gemacht aber eben keine INSERTs. Also das Control aktualisiert seine Datenquelle mit der neuen Zeile, es wird aber der Status nicht auf "Added" gesetzt, während "Modified" schon. Das ist das seltsame. Hätte jetzt erwartet, dass entweder beides geht oder nicht aber nicht so irgendwie halbe-halbe.

    Dass da nix anderes falsch läuft zeigt sich darin, dass ich nicht mehr machen muss als den Status der Entität auf Added zu setzen und sonst nichts weiter. Dann klappt's!


    Nofear23m schrieb:

    Hallo
    Du musst es der Context-Instanz so hinzufügen das der ChangeTracker es mitbekommt.
    db.DbSetName.Add(dieNeueEntität)
    und dann erst SaveChanges().

    Das Hinzufügen zum Context passiert ja durch das DataGrid. Im Debugger sehe ich die neuen Entitäten! Wird nur nix gesynct.

    Und ja, es ist WPF, da heißt das Dingens DataGrid
    Hallo

    MasterQ schrieb:

    Das Hinzufügen zum Context passiert ja durch das DataGrid.

    Ja, das Datagrid fügt es aber der ListOf hinzu und nicht dem DBSet des Contexts.

    MasterQ schrieb:

    Und ja, es ist WPF

    Und warum postest du dann nicht im WPF Bereich? Hat das einen Grund, sonst würde ich es verschieben.

    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 schrieb:


    Ja, das Datagrid fügt es aber der ListOf hinzu und nicht dem DBSet des Contexts.


    Schau nochmal in meinen ersten Post und dort die Methode zum Event. Das Event ist eines vom DataGrid und wird aufgerufen bevor das Einfügen einer neuen Zeile beendet wird. Das neu eingefügte Element ist über den Eventparameter e und da als e.NewItem erreichbar. Mit db.Entity(zug) greife ich darauf zu. Das neue Element des DataGrid ist zu diesem Zeitpunkt schon im Context vorhanden.

    MasterQ schrieb:

    Und ja, es ist WPF

    Und warum postest du dann nicht im WPF Bereich? Hat das einen Grund, sonst würde ich es verschieben.[/quote]

    Mein Problem äußert sich im nicht Vorhandensein von Datensätzen nach einem INSERT SQL-Kommando nachdem einem Steuerelement ein neuer Datensatz hinzugefügt wurde. Ich dachte, dass Leute die mit DBs arbeiten mir eher helfen können als andere.

    MasterQ schrieb:


    Mein Problem äußert sich im nicht Vorhandensein von Datensätzen nach einem INSERT SQL-Kommando nachdem einem Steuerelement ein neuer Datensatz hinzugefügt wurde. Ich dachte, dass Leute die mit DBs arbeiten mir eher helfen können als andere.


    Dachte auch, es wäre hier richtig... Nur wäre es nett gewesen, auch den WPF-Tag mit zu setzen, nicht nur Csharp.
    Sorry für's OT

    MasterQ schrieb:

    Mit db.Entity(zug) greife ich darauf zu. Das neue Element des DataGrid ist zu diesem Zeitpunkt schon im Context vorhanden.

    Und hast du es versucht mit .Add ???
    Ich habe deinen Post schon gelesen und auch verstanden und ich weis was der Code macht und eben auch was er nicht macht, der Changetracker ist aber ein kompliziertest konstrukt da nicht immer so verwendet werden kann wie man sich das denkt.

    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 schrieb:

    MasterQ schrieb:

    Mit db.Entity(zug) greife ich darauf zu. Das neue Element des DataGrid ist zu diesem Zeitpunkt schon im Context vorhanden.

    Und hast du es versucht mit .Add ???

    Mit Add arbeite ich an anderer Stelle, wo Daten nicht über eine Tabelle bearbeitet sondern neue Datensätze in einem Formular angelegt werden. Und da funktioniert das Add einwandfrei.


    ..., der Changetracker ist aber ein kompliziertest konstrukt da nicht immer so verwendet werden kann wie man sich das denkt.


    Neue Zeilen im DataGrid erhalten offensichtlich den Status "Detached", warum auch immer. Mir entzieht sich etwas der Sinn, neue Entitäten in den Context bzw. entsprechend in DBSet einzufügen und dann aber nicht den Status auf "Attached" bzw. "Added" zu setzen. Ist für mich grad irgendwie nur eine halbe Sache. Ich hät's noch verstanden wenn die neue Zeile nur in den Items des DataGrid auftauchen würde und nicht auch im Context. Was nützt eine Entität im Context, die "abgeschaltet" ist?

    Gehen wir mal davon aus, dass man sich was dabei gedacht hat.

    Gruß

    MQ
    Zum einen beantwortet das nicht meine Frage ob du .Add() in diesem Szenario versucht hast und zum anderen nutzt einem "Detached" sehr viel. Und zwar in Sachen performance.
    Man sollte NIE Daten mit Tracking abrufen solange man nicht vor hat den Changetracker auch zu nutzen, also entweder wenn man vorhat wie du manuell den State zu setzen oder wenn man Daten welche man abruft nicht bearbeiten möchte. Dann ruft man die Daten mit .AsNoTracking() ab. Meine Empfehlung für EF Core ist sowieso Tracking global abzuschalten und nur nach Bedarf (und nur für wenige Datensätze) mit .AsTracking() abzurufen.

    Es hat einen Grund warum das Thema EF Core ganze Bücher füllt.

    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 schrieb:

    Zum einen beantwortet das nicht meine Frage ob du .Add() in diesem Szenario versucht hast...


    Ich hatte geschrieben, dass ich .Add() an anderer Stelle einsetze. Das impliziert, dass ich es hier an dieser Stelle nicht tue. Warum auch?

    Mir ist nicht ganz klar, warum du hier nach Add fragst, Sasha.
    1. Sollte ich Add mal testen, z.B einen Dummy Eintrag zusätzlich anhängen?
    2. Oder sollte ich den Automatismus von DataGrid aushebeln, also den bereits angehängten Eintrag löschen und einen neuen, eigens dafür erstellten Eintrag mit .Add() anfügen?


    Gut, das mit der Performance ist ein Argument.

    MasterQ schrieb:

    Mit db.Entity(zug) greife ich darauf zu.
    kann eigentlich nicht funktionieren.
    Da greifst du auf ein Element zu, was noch nicht hinzugefügt wurde.
    Hast du das überprüft, ob ein Element, auf das zugegriffen wird, ehe es geadded wird, ob es sich dann selbst hinzufügt?
    Solche Konstruktionen sind möglich, aber sehr ungewöhnlich.
    Ob EF-EntityContext so drauf ist stelle ich infrage.

    ErfinderDesRades schrieb:

    MasterQ schrieb:

    Mit db.Entity(zug) greife ich darauf zu.
    kann eigentlich nicht funktionieren.

    Tut's aber! Mit dem Debugger kann ich auf alles von zug zugreifen. Das habe ich als erstes überprüft, von welchem Typ das Zeilenelement des DataGrid ist, ob dieses gleich oder zumindest kompatibel zu meiner Entitätsklasse ist und ob db.Entry (nicht Entity, sry Tippfehler) einen Fehler wirft.


    Da greifst du auf ein Element zu, was noch nicht hinzugefügt wurde.

    Ich hatte schon mehrfach geschrieben, dass es schon hinzugefügt ist.


    Hast du das überprüft, ob ein Element, auf das zugegriffen wird, ehe es geadded wird, ob es sich dann selbst hinzufügt?
    Solche Konstruktionen sind möglich, aber sehr ungewöhnlich.
    Ob EF-EntityContext so drauf ist stelle ich infrage.

    Das Szenario tritt nicht auf. Ich komme gar nicht ran an das Element bevor es hinzugefügt wurde.

    Nochmal Schritt für Schritt
    1. das DataGrid ist angezeigt, CanUserAddRows ist auf true
    2. Der Nutzer geht mit dem Cursor in eine Zelle der neu hinzuzufügenden Zeile
    3. Sobald eine Taste (Text) gedrückt wurde, wird das Event DataGrid_InitializingNewItem geschmissen (Jetzt ist klar, dass tatsächlich eine neue Zeile angefügt werden soll)
    4. In der Eventroutine kann mit e.NewItem auf die Instanz der neuen Zeile zugegriffen werden.
    5. Zu diesem Zeitpunkt existiert die neue Zeile schon im Speicher als Instanz eines Elements des Typs der Itemssource Collection.
    6. Zu diesem Zeitpunkt ist dieses Element auch schon im Context eingefügt (db.Entry(zug) schmeißt keinen Fehler!).
    7. Wenn das Event abgearbeitet ist, wird die neue Zeile samt Inhalt auf dem Bildschirm angezeigt.
    8. Löse ich dann ein db.SaveChanges() aus, dann wird die neue Zeile in die DB übertragen, sofern der Status auf "Added" gesetzt und nicht auf "Detached" belassen wurde.

    Ich als Programmierer muss da nichts mehr tun. Das Erstellen einer neuen Zeileninstanz, das Einfügen derselben in die Datenquelle des Grid und damit in DBSet des Contextes passiert automatisch hinter den Kulissen. Ich muss einzig und alleine den Status auf "Added" setzen.


    Verwirrend?

    -- Edit --
    • Ich habe in die Eventroutine noch ein .Add() mit einer einzig dafür erstellten Instanz der Entität eingefügt. Diese wird anstandslos übertragen ohne dass händisch der Status geändert werden muss.
    • Ein Zugriff auf eine Entität fügt diese nicht automatisch dem Context hinzu.

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

    MasterQ schrieb:

    Das impliziert, dass ich es hier an dieser Stelle nicht tue. Warum auch?


    Gut. Wenn du es besser weist stellt sich mir gerade die Frage warum du hier fragst.
    Fakt ist das das neue Element nicht dem ChangeTracker hinzugefügt wird, was mit .Add() aber passieren würde. Aber gut. Du machst das schon.

    Edit: @Akanel mit context.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking
    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 schrieb:

    Gut. Wenn du es besser weist stellt sich mir gerade die Frage warum du hier fragst.

    ?? Irgendwie scheinen wir aneinander vorbeizureden. Wenn ich missverstanden wurde, dann bitte ich um Entschuldigung.

    Ich behaupte nicht, ich wüsste es besser. Ganz im Gegenteil, ich bin in der Materie Anfänger und Laie!

    Du hattest vorgeschlagen, es mal mit .Add() zu versuchen. Ich habe nicht verstanden, welche Absicht dein Vorschlag hatte. Sollte das .Add() eine Art Test sein, ob das an der Stelle geht oder nicht oder sollte es ein Vorschlag für eine finale Lösung gewesen sein, den ganzen Automatismus zu umgehen und die neuen Zeilen eigen händisch mit .Add() einzufügen?

    Sollte es letzteres gewesen sein, sehe ich als Laie keinen Sinn drin, den vorhandenen Automatismus auszuhebeln und selbst etwas anders zu bauen nur um das Setzen eines Flags zu sparen.


    Fakt ist das das neue Element nicht dem ChangeTracker hinzugefügt wird, was mit .Add() aber passieren würde.


    Das ist die Erklärung des Phänomens in Kurzform. Mir erschließt sich aber dennoch nicht, warum man bestehende Datensätze attached einfügt und neu hinzugekommene nicht. Ich hätte erwartet, dass man beim Design des Frameworks sich dafür entschieden hätte, alle Einträge per Default zu tracken oder eben per Default nicht. Aber so und mal so ist nach meinem Verständnis etwas inkonsequent.

    Das was ich zum Schluss aufgelistet habe ist meine Erkenntnis, nachdem ich die letzten zwei Tage mit dem Debugger durch jede Ritze bin. Anderen war das wohl auch nicht so klar wie das im Einzelschritt abläuft.


    Gruß

    MQ

    MasterQ schrieb:

    ErfinderDesRades schrieb:

    MasterQ schrieb:

    Mit db.Entity(zug) greife ich darauf zu.
    kann eigentlich nicht funktionieren.

    Tut's aber! Mit dem Debugger kann ich auf alles von zug zugreifen. Das habe ich als erstes überprüft, von welchem Typ das Zeilenelement des DataGrid ist, ob dieses gleich oder zumindest kompatibel zu meiner Entitätsklasse ist und ob db.Entry (nicht Entity, sry Tippfehler) einen Fehler wirft.


    Da greifst du auf ein Element zu, was noch nicht hinzugefügt wurde.

    Ich hatte schon mehrfach geschrieben, dass es schon hinzugefügt ist.
    Ja, das hast du geschrieben, aber wenn ich den Code aus Post#1 sehe:

    C#-Quellcode

    1. //verlorene Zeilen...
    2. db = new MeinDBContext();
    3. var Daten = db.Entities.Where(xx=>xx.Bedingung==true).ToList();
    4. dataGrid.Itemssource = Daten;
    5. ...
    6. //ende verlorene Zeilen...
    7. private void DataGrid_InitializingNewItem(object sender, InitializingNewItemEventArgs e) {
    8. Entity zug = e.NewItem as Entity;
    9. zug.Ordinal = (dataGrid.Items.IndexOf(zug) + 1) * 10;
    10. zug.PlanID = PlanID;
    11. db.Entry(zug).State = EntityState.Added;
    12. }
    Dann stimmt das einfach nicht.
    Also wenn der Eintrag per Datagrid zugefügt wurde, dann ist er einer Auflistung namens Daten zugefügt - aber nicht(!) dem db.Entities-Object.

    Oder da ist noch mehr Code am Werk, den du nicht gezeigt hast.

    ErfinderDesRades schrieb:


    Oder da ist noch mehr Code am Werk, den du nicht gezeigt hast.


    Nein, da ist kein sonstiger Code!

    Ich bin heute morgen mit dem Debugger noch mal intensiv durch alles durch und ich denke, ich habe den Übeltäter.

    Es ist die Zeile

    C#-Quellcode

    1. ​db.Entry(zug).State = EntityState.Added;
    .

    Für .Entry() findet man folgende Erläuterung in der Doku


    Entry<TEntity>(TEntity)
    Ruft ein DbEntityEntry<TEntity>-Objekt für die angegebene Entität ab, das Zugriff auf Informationen über die Entität und die Fähigkeit zum Ausführen von Aktionen für die Entität bereitstellt.


    Das ist so doch etwas stark verkürzt und missverständlich, erklärt zumindest nicht alles was das Teil macht. Ich hatte das so verstanden, dass ich mit .Entry() an weitere Informationen zu Entitäten ran komme, so eben wie den Status und anderes. Möööp, falscher Fehler. .Entry() fügt die Entität auch dem Context hinzu. Das lässt sich beobachten, wenn man sich die lokale Kopie des DBSet vor und nach dem .Entry() ansieht.

    Und das hat mich auf die falsche Fährte gelockt. Zunächst stand die Frage im Raum, ob die neue Zeile auch automatisch dem Context hinzugefüht wird. Dazu habe ich .Entry() verwendet. Nach meinem Verständnis hätte .Entry() für eine nicht existente Entität im Context einen Fehler schmeißen müssen. Als es das nicht tat, war für mich klar, dass die neue Zeile schon im Context ist und später dann, dass ich den Status per Hand setzen muss.

    Ich dachte, dass ich mit dem Setzen des Status auf "Added" nur den Status verändere und nicht noch ein verkapptes bzw. verstümmeltes Add(). Das ist wahrscheinlich das, was Sasha in seinen Posts meinte.

    Bin ich denn jetzt mit dem was ich zu .Entry() verbinde auf dem richtigen Pfad oder immer noch im Nebel stochernd??

    Gruß

    MQ