【问题标题】:Breaking POCO relationships打破 POCO 关系
【发布时间】:2026-01-18 19:45:01
【问题描述】:

我在破坏两个实体之间的关系时遇到问题。假设我们有一个Child 实体属于一个Parent,一个Parent 有许多Child 实体。现在在我的前端,用户可以选择一个单选按钮来选择Parent 属于哪个Child,或者他们可以选择“无”。我遇到的问题是,当我编辑 Child 记录并选择“无”时,它似乎并没有破坏现有关系。

在我的业务逻辑中,我正在执行以下操作:

child.Parent = parentRepository.Find(command.ParentID);

如果没有记录与传入的 ID 匹配,则存储库方法返回 null,并且选择“无”给出的 ID 为 0,但当我调试并跨过这一行时,Child.Parent 仍然具有对先前选择的 Parent 实体的引用.事实上,甚至显式设置 Child.Parent = null;似乎没有断开链接。

为了增加我的困惑,这不是一致的行为。时不时地使用相同的代码确实破坏关系并将 Child.Parent 设置为 null!有人可以指出我正确的方向吗?

更新

我更新了我的代码以确保 repo 返回 null。

Parent parent = parentRepository.Find(command.ParentID);
if (parent == null)
    child.parent = null;
else
    child.parent = parent;

调试显示 child.parent = null;is 正在执行,但是当我在此行之后检查 child.parent 时,它仍然显示对先前引用的 Parent 对象的引用。但时不时地,它被正确设置为空。此版本有效:

Parent parent = parentRepository.Find(command.ParentID);
if (parent == null)
{
    child.parent = null;
    child.parent = null;
}
else
    child.parent = parent;

为什么我必须将其设置为 null 两次才能获得一致的行为?我是否必须以某种方式明确声明ParentChild 实体上可以为​​空?

【问题讨论】:

  • 也粘贴子模型代码。如果您在 OnModelCreating 事件中执行任何流畅的 API 调用,请同时显示它们。

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


【解决方案1】:

是的,您确实需要声明父级可以为空。

如果您在子级上公开外键属性,它必须可以为空。因此,如果您的孩子有 ParentId 属性,它应该如下所示:

public int? ParentId { get; set; }
public virtual Parent { get; set; }

如果您使用的是 fluent API,您可以像这样告诉 EF 父级可以为空:

modelBuilder.Entity<Child>.HasOptional(d => d.Parent).WithMany(p => p.Children);

或者像这样,如果你从主体端而不是从属端声明关系:

modelBuilder.Entity<Parent>.HasMany(p => p.Children).WithOptional(d => d.Parent);

奇怪的是child.Parent需要设置两次为null。您是否尝试过进入代码以查看原因?您也可以为您的 Parent 属性试试这个,看看到底发生了什么:

private Parent _parent;
public virtual Parent
{
    get { return _parent; }
    set { _parent = value; }
}

这是您可以实际进入的代码,以确保将私有字段设置为 null。

对评论的回应

听起来确实存在延迟加载的问题。试试这个。

在您的存储库 Find 方法中,急切加载父级。你可以这样做:

context.Children.Include(c => c.Parent).Find(id);

这将使 child.Parent 已经加载到上下文中,设置为 null 一次就足够了。

【讨论】:

  • 好的,所以我按照建议更改了 Parent 属性,看看发生了什么。调用集合时,_parent 为 null,并且它保持为 null,直到我将光标悬停在公共虚拟父级上!然后它会水合,此时我可以成功地将其设置为空。如果我不悬停,它仍然为空,就像它没有从数据库中拉回记录一样,所以当我将它设置为空时,它什么也不做。这是某种延迟加载吗?我会认为当我调用 child.parent 时,EF 会为 Parent 补水?
  • 看起来我得到的行为是正常的。因为 Parent 导航属性是虚拟的,所以延迟加载适用。似乎在第一次调用导航属性时,相关对象从数据库中得到了水合。因此,当我使用 child.parent = null 将其设置为 null;所有代码真正做的是从数据库中获取记录。因此为什么第二个 child.parent = null;尝试工作。无论如何,感谢您的回答,它确实帮助我解决了这个问题。