EF 实体在更新时未保存相同实体类型的子属性
Posted
技术标签:
【中文标题】EF 实体在更新时未保存相同实体类型的子属性【英文标题】:EF entity is not saving child property of same entity type on update 【发布时间】: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);
【问题讨论】:
【参考方案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");
【讨论】:
以上是关于EF 实体在更新时未保存相同实体类型的子属性的主要内容,如果未能解决你的问题,请参考以下文章