【问题标题】:EF entity is not saving child property of same entity type on updateEF 实体在更新时未保存相同实体类型的子属性
【发布时间】:2017-08-26 04:44:15
【问题描述】:

我正在使用 ASP.NET MVC 5 和 Entity Framework 6。我有一个页面允许用户输入进程信息。此信息的一个方面是从启动过程的下拉列表中进行选择。这个类大致是这样的:

**

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

**

我正在使用一个视图模型,它具有 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();
    }
  }

我的编辑帖子操作如下所示:

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

问题在于,虽然 vm.SelectedStartProcess 不为 null 并且具有适当的值,但它永远不会保存到数据库中。数据库将此字段显示为 StartProcess_ProcessId。另外值得注意的是,一个进程可能有 0 或 1 个正在启动的进程。

我想知道 EF 已将数据库表中的此属性设为外键(本质上指向同一个表)这一事实是否会导致问题。我很乐意听取建议。

我还要补充一点,“创建帖子”操作按预期工作。这一定与向 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);
    }

【问题讨论】:

    标签: c# asp.net-mvc entity-framework asp.net-mvc-4 entity-framework-6


    【解决方案1】:

    没有显式原始 FK 属性的引用导航属性的问题是影子 FK 状态由从中检索实体的DbContext 维护。在像您这样的断开连接的情况下,哪些信息会丢失。

    当你调用时

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

    对于断开连接的实体,EF 会将实体附加到上下文并将所有 原始 属性标记为已修改。参考导航属性被视为未更改,因此在您调用 SaveChanges 时不会更新它们。

    要让 EF 更新 FK,了解原始和新的参考属性值至关重要。在您的情况下,传递的 SupportProcess 实体的 StartProcess 属性的原始值。

    由于在断开连接的情况下(尤其是像你这样的延迟加载属性)你不能依赖传递的对象属性值,我建议以下安全的操作顺序:

    (1) 将导航属性设置为null 以避免将值附加到上下文。
    (2) 将实体附加到上下文并将其标记为已修改。
    (3) 显式加载导航属性。这将导致额外的数据库故障,但会确保更新正常工作。
    (4) 设置导航属性的新值。当您调用SaveChanges 时,上下文更改跟踪器将能够确定是否需要更新 FK。

    将其应用于您的案例:

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

    【讨论】:

    • 该代码逐字运行,您的解释非常出色。这正是我申请赏金时所希望的那种答案!直到明天我才能给你赏金,但这是你的。感谢您提供出色、信息丰富且正确的回复!
    【解决方案2】:

    我认为您的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);
    }
    

    【讨论】:

    • 我认为您是对的,但是该语句在其余代码中的位置很重要。你能指出 OP 的代码应该是什么样子吗?
    • 我会在那个星期一试试。还想知道我的编辑是否需要获取子 StartProcess 对象的 ProcessId 并将其作为隐藏字段包含在页面上,因此它最初可能是模型绑定在帖子和数据库上下文的一部分上。明天两个都试试。谢谢!
    • 我试过这个。没有骰子。更新顺利进行,但 vm.SupportProcess.StartProcess.ProcessId 值绝不是更新的一部分。我实际上正在使用 Sql Profiler,所以我看到了针对数据库运行的确切 sql,并且我想要更新的字段除了在创建时从未包含在内。
    【解决方案3】:

    确保您的模型配置正确:

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

    还有你的编辑方法:

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

    【讨论】:

      猜你喜欢
      • 2021-11-06
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-10-28
      • 2014-12-03
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多