Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

EF entity is not saving child property of same entity type on update

I am using 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 to choose from a drop down the Starting Process. This class roughly looks like:

**

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 am using a view model that has properties for the SupportProcess, the selected start process, and a list of processes to populate the drop down on the view.

  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:

   [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 issue is that, while vm.SelectedStartProcess is not null and has a proper value, it never gets saved to the database. The database shows this field as StartProcess_ProcessId. Also of note is that a process may have 0 or 1 Starting processes.

I'm wondering if the fact that EF has made this property in the database table a foreign key, which would, in essence, point to the same table, is somehow causing the issue. I'm all ears for suggestions.

enter image description here

I'll also add that the Create Post action works as expected. This must be something to do with conveying to EF that the StartProcess property also needs to be updated on the entity..That's a guess 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);
    }
like image 653
jason Avatar asked Mar 31 '17 19:03

jason


People also ask

How does DbContext change state of entity?

This can be achieved in several ways: setting the EntityState for the entity explicitly; using the DbContext. Update method (which is new in EF Core); using the DbContext. Attach method and then "walking the object graph" to set the state of individual properties within the graph explicitly.


1 Answers

The problem with reference navigation property without explicit primitive FK property is that the shadow FK state is maintained by the DbContext from which the entity has been retrieved. Which information is lost in disconnected scenario like yours.

When you invoke

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

with disconnected entity, EF will attach the entity to the context and mark all primitive properties as modified. The reference navigation properties are considered unchanged, thus not updating them when you call SaveChanges.

To let EF update the FK, it's critical to know both original and new reference property value. In your case, the original value of StartProcess property of the passed SupportProcess entity.

Since in disconnected scenarios (and especially with lazy loaded properties like yours) you can't rely on the passed object property value, I would suggest the following safe sequence of operations:

(1) Set the navigation property to null to avoid attaching the value to the context.
(2) Attach the entity to the context and mark it as modified.
(3) Explicitly load the navigation property. This will cause additional database trip, but will ensure the update is working.
(4) Set the new value of the navigation property. The context change tracker will be able to determine if FK update is needed 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);
}
like image 148
Ivan Stoev Avatar answered Nov 03 '22 00:11

Ivan Stoev