简体   繁体   English

EF实体在更新时不保存相同实体类型的子属性

[英]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 . 我正在使用ASP.NET MVC 5和Entity Framework 6.我有一个页面,允许用户输入流程信息 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. 我使用的视图模型具有SupportProcess的属性,选定的启动过程以及用于填充视图下拉列表的进程列表。

  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. 问题是,虽然vm.SelectedStartProcess不是null并且具有适当的值,但它永远不会保存到数据库中。 The database shows this field as StartProcess_ProcessId . 数据库将此字段显示为StartProcess_ProcessId Also of note is that a process may have 0 or 1 Starting processes. 另外值得注意的是,一个进程可能有0或1个启动进程。

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. 我想知道EF是否在数据库表中使这个属性成为一个外键,实际上它指向同一个表,这在某种程度上导致了这个问题。 I'm all ears for suggestions. 我全都听取了建议。

在此输入图像描述

I'll also add that the Create Post action works as expected. 我还要补充一点,Create Post操作按预期工作。 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... 这必须与传达给EF的事情有关,即StartProcess属性也需要在实体上更新。但这是猜测...

    [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);
    }

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. 没有显式原始FK属性的引用导航属性的问题是,影子FK状态由已DbContext检索实体的DbContext维护。 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. 对于已断开连接的实体,EF会将实体附加到上下文,并将所有原始属性标记为已修改。 The reference navigation properties are considered unchanged, thus not updating them when you call SaveChanges . 参考导航属性被视为未更改,因此在调用SaveChanges时不会更新它们。

To let EF update the FK, it's critical to know both original and new reference property value. 要让EF更新FK,了解原始参考属性值和新参考属性值至关重要。 In your case, the original value of StartProcess property of the passed SupportProcess entity. 在您的情况下,传递的SupportProcess实体的StartProcess属性的原始值。

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. (1)将navigation属性设置为null以避免将值附加到上下文。
(2) Attach the entity to the context and mark it as modified. (2)将实体附加到上下文并将其标记为已修改。
(3) Explicitly load the navigation property. (3)显式加载导航属性。 This will cause additional database trip, but will ensure the update is working. 这将导致额外的数据库跳闸,但将确保更新正常。
(4) Set the new value of the navigation property. (4)设置导航属性的新值。 The context change tracker will be able to determine if FK update is needed or not when you call SaveChanges . 当您调用SaveChanges时,上下文更改跟踪器将能够确定是否需要FK更新。

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);
}

I think your Edit ActionResult should look something like this 我认为你的Edit ActionResult看起来应该是这样的

    [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);
}

Make sure your model is configured properly: 确保您的模型配置正确:

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 edit 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");
}    

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM