当我在实体上 GetById() 然后将子实体的集合设置为来自 MVC 视图的新列表时,我收到此错误。
操作失败:无法更改关系,因为一个或多个外键属性不可为空。当对关系进行更改时,相关的外键属性将设置为空值。如果外键不支持空值,则必须定义新的关系,必须为外键属性分配另一个非空值,或者必须删除不相关的对象。
我不太明白这一行:
无法更改关系,因为一个或多个外键属性不可为空。
为什么我要改变两个实体之间的关系?它应该在整个应用程序的整个生命周期中保持不变。
发生异常的代码是将集合中修改后的子类简单地分配给现有的父类。这有望满足删除子类、添加新类和修改的需求。我原以为实体框架会处理这个问题。
代码行可以提炼为:
var thisParent = _repo.GetById(1); thisParent.ChildItems = modifiedParent.ChildItems(); _repo.Save();
您应该手动删除旧的子项thisParent.ChildItems。实体框架不会为您做到这一点。它最终无法决定你想对旧的子项目做什么——如果你想把它们扔掉,或者你想保留它们并将它们分配给其他父实体。您必须告诉 Entity Framework 您的决定。但是您必须做出这两个决定之一,因为如果不引用数据库中的任何父实体(由于外键约束),子实体就不能单独生存。这基本上就是异常所说的。
thisParent.ChildItems
编辑
如果可以添加、更新和删除子项目,我会怎么做:
public void UpdateEntity(ParentItem parent) { // Load original parent including the child item collection var originalParent = _dbContext.ParentItems .Where(p => p.ID == parent.ID) .Include(p => p.ChildItems) .SingleOrDefault(); // We assume that the parent is still in the DB and don't check for null // Update scalar properties of parent, // can be omitted if we don't expect changes of the scalar properties var parentEntry = _dbContext.Entry(originalParent); parentEntry.CurrentValues.SetValues(parent); foreach (var childItem in parent.ChildItems) { var originalChildItem = originalParent.ChildItems .Where(c => c.ID == childItem.ID && c.ID != 0) .SingleOrDefault(); // Is original child item with same ID in DB? if (originalChildItem != null) { // Yes -> Update scalar properties of child item var childEntry = _dbContext.Entry(originalChildItem); childEntry.CurrentValues.SetValues(childItem); } else { // No -> It's a new child item -> Insert childItem.ID = 0; originalParent.ChildItems.Add(childItem); } } // Don't consider the child items we have just added above. // (We need to make a copy of the list by using .ToList() because // _dbContext.ChildItems.Remove in this loop does not only delete // from the context but also from the child collection. Without making // the copy we would modify the collection we are just interating // through - which is forbidden and would lead to an exception.) foreach (var originalChildItem in originalParent.ChildItems.Where(c => c.ID != 0).ToList()) { // Are there child items in the DB which are NOT in the // new child item collection anymore? if (!parent.ChildItems.Any(c => c.ID == originalChildItem.ID)) // Yes -> It's a deleted child item -> Delete _dbContext.ChildItems.Remove(originalChildItem); } _dbContext.SaveChanges(); }
注意:这未经测试。假设子项集合的类型为ICollection. (我通常有IList,然后代码看起来有点不同。)我还剥离了所有存储库抽象以保持简单。
ICollection
IList
我不知道这是否是一个好的解决方案,但我相信必须按照这些方式进行一些艰苦的工作,以处理导航集合中的各种变化。我也很高兴看到一种更简单的方法。