An EF object does not save a child property of the same entity type when updating

I use ASP.NET MVC 5 and Entity Framework 6. I have a page that allows the user to enter process information. One aspect of this information is the selection of the startup process from the drop-down list. This class looks something like this:

**

public class SupportProcess { [Key] public int ProcessId { get; set; } [DisplayName("Starting process?")] public virtual SupportProcess StartProcess { get; set; } public string Name { get; set; } [DisplayName("When is this run?")] public virtual ProcessSchedule ProcessSchedule { get; set; } [DisplayName("")] public string Description { get; set; } [DisplayName("Expected Result")] public string ExpectedResult { get; set; } } 

**

I use a view model that has properties for SupportProcess, the selected startup process, and a list of processes to populate the drop-down list.

  public class SupportProcessViewModel { public SupportProcess SupportProcess { get; set; } public int SelectedStartProcess { get; set; } public List<SupportProcess> Processes { get; set; } public SupportProcessViewModel() { this.SupportProcess = new SupportProcess(); } } 

My Edit post action looks like this:

  [HttpPost] [ValidateAntiForgeryToken] public ActionResult Edit(SupportProcessViewModel vm) { if (ModelState.IsValid) { if (vm.SelectedStartProcess > 0) { vm.SupportProcess.StartProcess = db.SupportProcesses.Find(vm.SelectedStartProcess); } db.Entry(vm.SupportProcess).State = EntityState.Modified; db.SaveChanges(); return RedirectToAction("Index"); } return View(vm); } 

The problem is that although vm.SelectedStartProcess is not null and has the correct value, it is never stored in the database. The database shows this field as StartProcess_ProcessId . It should also be noted that a process can have 0 or 1 initial processes.

I am wondering if the fact that EF made this property in the database table a foreign key, which essentially points to the same table, is somehow causing a problem. I have all the ears for suggestions.

enter image description here

I will also add that the Create Post action works as expected. This should be due to passing EF that the StartProcess property also needs to be updated on the object. This is an assumption though ...

  [HttpPost] [ValidateAntiForgeryToken] public ActionResult Create( SupportProcessViewModel vm) { if (ModelState.IsValid) { if(vm.SelectedStartProcess > 0) { vm.SupportProcess.StartProcess = db.SupportProcesses.Find(vm.SelectedStartProcess); } db.SupportProcesses.Add(vm.SupportProcess); db.SaveChanges(); return RedirectToAction("Index"); } return View(vm); } 
+7
c # asp.net-mvc entity-framework asp.net-mvc-4 entity-framework-6
source share
3 answers

The problem with a referenced navigation property without an explicit primitive FK property is that the FK shadow state is maintained by the DbContext from which the object was derived. What information is lost in a disconnected script such as yours.

On call

 db.Entry(entity).State = EntityState.Modified; 

with the entity disabled, EF will attach the object to the context and mark all properties of the primitive as changed. Navigation link properties are considered unchanged, thus not updating them when calling SaveChanges .

In order for EF to update FK, it is important to know both the original and the new value of the reference property. In your case, the initial value of the StartProcess property of the passed SupportProcess object.

Since in disabled scripts (and especially with lazy loadable properties like yours) you cannot rely on the property value of the passed object, I would suggest the following safe sequence of operations:

(1) Set the navigation property to null to avoid binding the value to the context.
(2) Attach the object to the context and mark it as modified.
(3) Explicitly load the navigation property. This will cause an extra trip to the database, but will ensure that the update will work.
(4) Set a new value for the navigation property. The context tracking tracker will be able to determine if an FK update is required or not when you call SaveChanges .

Applying it to your case:

 [HttpPost] [ValidateAntiForgeryToken] public ActionResult Edit(SupportProcessViewModel vm) { if (ModelState.IsValid) { // (1) vm.SupportProcess.StartProcess = null; // (2) db.Entry(vm.SupportProcess).State = EntityState.Modified; // (3) db.Entry(vm.SupportProcess).Reference(e => e.StartProcess).Load(); // (4) vm.SupportProcess.StartProcess = vm.SelectedStartProcess > 0 ? db.SupportProcesses.Find(vm.SelectedStartProcess) : null; db.SaveChanges(); return RedirectToAction("Index"); } return View(vm); } 
+6
source share

I think your Edit ActionResult should look something like this.

  [HttpPost] [ValidateAntiForgeryToken] public ActionResult Edit(SupportProcessViewModel vm) { if (ModelState.IsValid) { if (vm.SelectedStartProcess > 0) { vm.SupportProcess.StartProcess = db.SupportProcesses.Find(vm.SelectedStartProcess); } db.SupportProcess.Attach(vm.SupportProcess); db.Entry(vm.SupportProcess).State = EntityState.Modified; db.SaveChanges(); return RedirectToAction("Index"); } return View(vm); } 
0
source share

Make sure your model is configured correctly:

 public class SupportProcess { [Key] public int ProcessId { get; set; } [DisplayName("Starting process?")] public int StartProcessId { get; set; } [ForeignKey("StartProcessId")] public virtual SupportProcess StartProcess { get; set; } public string Name { get; set; } [DisplayName("When is this run?")] public virtual ProcessSchedule ProcessSchedule { get; set; } [DisplayName("")] public string Description { get; set; } [DisplayName("Expected Result")] public string ExpectedResult { get; set; } } 

And your editing method:

 [HttpPost] [ValidateAntiForgeryToken] public ActionResult Edit(SupportProcessViewModel vm) { if (!ModelState.IsValid) { return View(vm); { // Get the item var sp = db.SupportProcesses.FirstOrDefault(p => p.Id == vm.SupportProcess.ProcessId); // Set the new values if (vm.SelectedStartProcess > 0) { sp.StartProcessId = vm.SelectedStartProcess; } sp.Name = vm.SupportProcess.Name; sp.Description = vm.SupportProcess.Description; // all the rest values // Save changes db.SupportProcesses.Update(sp); db.SaveChanges(); return RedirectToAction("Index"); } 
0
source share

All Articles