小编典典

无法更改关系,因为一个或多个外键属性不可为空

all

当我在实体上 GetById() 然后将子实体的集合设置为来自 MVC 视图的新列表时,我收到此错误。

操作失败:无法更改关系,因为一个或多个外键属性不可为空。当对关系进行更改时,相关的外键属性将设置为空值。如果外键不支持空值,则必须定义新的关系,必须为外键属性分配另一个非空值,或者必须删除不相关的对象。

我不太明白这一行:

无法更改关系,因为一个或多个外键属性不可为空。

为什么我要改变两个实体之间的关系?它应该在整个应用程序的整个生命周期中保持不变。

发生异常的代码是将集合中修改后的子类简单地分配给现有的父类。这有望满足删除子类、添加新类和修改的需求。我原以为实体框架会处理这个问题。

代码行可以提炼为:

var thisParent = _repo.GetById(1);
thisParent.ChildItems = modifiedParent.ChildItems();
_repo.Save();

阅读 31

收藏
2022-06-23

共1个答案

小编典典

您应该手动删除旧的子项thisParent.ChildItems。实体框架不会为您做到这一点。它最终无法决定你想对旧的子项目做什么——如果你想把它们扔掉,或者你想保留它们并将它们分配给其他父实体。您必须告诉
Entity Framework
您的决定。但是您必须做出这两个决定之一,因为如果不引用数据库中的任何父实体(由于外键约束),子实体就不能单独生存。这基本上就是异常所说的。

编辑

如果可以添加、更新和删除子项目,我会怎么做:

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,然后代码看起来有点不同。)我还剥离了所有存储库抽象以保持简单。

我不知道这是否是一个好的解决方案,但我相信必须按照这些方式进行一些艰苦的工作,以处理导航集合中的各种变化。我也很高兴看到一种更简单的方法。

2022-06-23