Laden...

EF - Fremdschlüssel wird nicht aktualisiert

Letzter Beitrag vor 11 Jahren 10 Posts 1.131 Views
EF - Fremdschlüssel wird nicht aktualisiert

Hallo,

ich arbeite erst seit kurzem mit dem EF.
Jetzt habe ich folgendes Problem:

Ich habe eine Klasse MenuItem


public class MenuItem
    {
        public int Id { get; set; }
        public int Sort { get; set; }
        public String Name { get; set; }
          
        public virtual MenuGroup MenuGroup { get; set; }
    }

Ein MenuItem gehört zu einer MenuGroup.
Beim Anlegen eines neuen MenuItems wird alles korrekt in der Datenbank angelegt, also
die normalen Attribute des MenuItems inkl. ID der Group als Fremdschlüssel.

Allerdings wird bei Änderung der MenuGroup eines vorhandenen MenuItems der Fremdschlüssel nicht aktualisiert.

Hier meine Edit-Methode:


[HttpPost]
        public ActionResult MenuEdit(MenuItem menuItem)
        {
            if (ModelState.IsValid)
            {
                db.Entry(menuItem).State = EntityState.Modified;
                db.SaveChanges();
                return RedirectToAction("Menu");
            }
            return View(menuItem);
        }

Das Objekt welches der Methode übergeben wird hat zum Zeitpunkt von db.SaveChanges(); die richtige MenuGroup. Alle anderen geänderten Attribute werden korrekt übernommen. Ändere ich den **EntityState **in Added wird ein korrekter neuer Eintrag in die DB geschrieben.

Warum nicht bei Modified?

Vielen Dank schonmal für die Mühe

Hallo ashrak,

dein "Problem" ist hier, dass deine Eigenschaft MenuGroup hier eine independent association ist. Heißt, dass durch ein setzen des EntityStates auf deinem MenuItem dieser EntityState nicht auch für diese Relation gilt. Der StateTracker hält für independent associations einen eigenen State. Da du hier vermutlich die Aktualisierung in verschiedenen Datenbankkontexten durchführst, sprich an disconected entities, weiß der StateTracker zum Zeitpunkt der Aktualisierung nichts über die geänderten Relationen.

Du hast zwei Möglichkeiten:

  1. (die einfachste): Du nutzt keine Navigation Properties sondern direkte IDs.
    Sprich dein POCO würde z.B. so aussehen:
public class MenuItem
{
     public int Id { get; set; }
     public int Sort { get; set; }
     public String Name { get; set; }

     public int MenuGroupID { get; set; }
}

Dadurch wirkt sich eine Änderung der Eigenschaft auch direkt auf das Element aus. Jedoch kann dieser Ansatz nicht für many-to-many Eigenschaften genutzt werden - soweit ich weiß.

  1. Du musst den StateManager darüber in Kenntnis setzen, dass sich die Eigenschaft geändert hat. Bedeutet, dass du eine recht komplexe Updateroutine erstellen musst.

Ich hoffe ich konnte einige Klarheiten beseitigen 😃

Gruß,
Geaz

Hi,

ich möchte ungern auf IDs gehen.
Wie funktioniert die 2. Möglichkeit?

ich hatte es schonmal damit versucht:

db.Entry(menuItem).State = EntityState.Added;
db.Entry(menuItem.MenuGroup).State = EntityState.Modified;
db.SaveChanges();

das hat aber genauso wenig funktioniert.

Du kannst den State im ObjectTracker selbst neu setzen. DIes ist z.B. in diesem Buch beschrieben: Programming Entity Framework: DBContext

Beispiel:


db.Entry(previousMenuGroup).State = EntityState.Unchanged;
((IObjectContextAdapter)context).ObjectContext
	.ObjectStateManager
	.ChangeRelationshipState(
	menuItem,
	menuItem.MenuGroup,
	l => l.MenuGroup,
	EntityState.Added);
	
((IObjectContextAdapter)context).ObjectContext
	.ObjectStateManager
	.ChangeRelationshipState(
	menuItem,
	previousMenuGroup,
	l => l.MenuGroup,
	EntityState.Deleted);

Ist zwar umständlich, aber so klappts.

Gruß,
Geaz

Das Problem ist hier auch der Einsatz von ASP.NET MVC.

Das Prinzip, dass Entities an einer Action erwartet werden und dann auch noch in Kombination mit dem ModelState ist von Microsoft leider nicht zu Ende gedacht worden.
Das Vorgehen, das in den Tutorials NerdDinner und MusicStore gezeigt werden ist einfach nicht realitätsnah und nur dafür geeignet, dass man das Grundprinzip versteht.
Vieles, was dort gezeigt wird, kann man viel viel besser, (manchmal) einfacher und vor allem sicherer sowie modularer umsetzen.

Warum?
* Es sollte niemals mehr Infos an die View gehen als nötig -> mit ViewModels arbeiten
* Es sollten nur die Daten an einer Action empfangen werden, die auch in diesem Falle möglich oder nötig sind -> mit SubmitModels arbeiten.
Ansonsten gibt es unschöne Effekte, dass Relationen nicht aktualisiert werden oder gelöscht werden (je nach eingesetzter EF Version).
Zudem funktioniert die Validierung nicht auf Werte (ModelState), die erst nachträglich hinzugefügt werden können (wie eben Relationen).

Daher bei ASP.NET MVC mein Rat, den ich hier schon sehr oft kundt getan hab:
* ModelState von ASP.NET MVC deaktivieren.
* Validierung selbst übernehmen und implementieren, in dem IValidatableObject verwendet wird
* Immer mit ViewModels und SubmitModels arbeiten - auch der Code-Struktur und des Pflegeaufwands zu liebe
* Bei Code First NavigationProperties mit ID-Feldern kombinieren und das im manuellen Mapping als FK definieren
=> Fördert die schleckliche EF-Performance bei Mass-Inserts und macht auch so einige Dinge etwas einfacher.

Hallo,

da die Webanwendung an sich ja statuslos ist, mache ich es bei geänderten Datensätzen immer so, dass ich zunächst (anhand der ID) noch einmal den original-Datensatz lese, dann die Felder neu setze und anschließend SaveChanges aufrufe.

var item = db.MenuItems.Where(m => m.Id == Id).Single();

/* Änderungen */

item.Sort = model.Sort;
item.Name = model.Name;
item.MenuGroup = model.MenuGroup;

db.SaveChanges();

Dann noch bitte den Post von Abt beachten: Entities gehören nicht in die View. Setze View- und Editmodels ein. Es sieht zunächst vielleicht komplizierter aus, aber Du vermeidest viele Fehler und unnötigen overhead.

--
mfG.
Marcel Eckhoff

Aber auch für Dich wäre ein Repository Pattern angeraten, Profox 😉

Sry, hast Du natürlich recht. Ich mache das in meinen Projekten so, hier nicht bedacht. Aber diesbezüglich gibt es hier im Forum noch Quellen. 😃

--
mfG.
Marcel Eckhoff

da die Webanwendung an sich ja statuslos ist, mache ich es bei geänderten Datensätzen immer so, dass ich zunächst (anhand der ID) noch einmal den original-Datensatz lese, dann die Felder neu setze und anschließend SaveChanges aufrufe.

Auch eine Möglichkeit, welche ich einsetze. Jedoch sollte man sich hier im klaren sein, dass dadurch durch jedes Update ein zusätzliches Select auftritt. Das mag zwar bei einem Select über eine ID nicht extrem ins Gewicht fallen, aber es ist dennoch zusätzlicher Aufwand.
Jedoch finde ich diese Möglichkeit auch einfacher 😃

..und bei Webanwendungen sollte man niemals eine andere Möglichkeit als die von Profox in Betracht ziehen - nie.
Daher ist dieser zusätzliche Select ein absolutes Muss.