Entity Framework导航属性保存前为空,实体创建的正确工作流是什么?
我有一个需保存至数据库的树形数据基础示例:
class TreeModel { int id; virtual Collection<ParentNode> ParentNodes; } class ParentNode { int id; virtual TreeModel TreeModel; virtual Collection<ChildNode> ChildNodes; } class ChildNode { int id; virtual ParentNode Parent; }
创建该树形结构的流程分布在多个表单中:
// 添加Model按钮 tree = new TreeModel(); dbContext.Models.Add(tree); // 变更跟踪日志 // 添加Node按钮: tree.ParentNodes.Add(new ParentNode()); new ParentForm(tree.ParentNodes.First()); // 在ParentForm中: ParentNode.ChildNodes.Add(new ChildNode());
上述流程可正常运行,但未调用Add()的导航属性始终为null,仅在调用上下文的SaveChanges()后才会解析这些属性并设置id。我原以为由于TreeModel已被跟踪,EF会自动检测导航属性的变更并解析。
这带来了问题:当打开Form(ParentNode)并想在表单中显示TreeModel的属性(如名称),或在创建向导中复制另一个ParentNode的ChildNode时,无法遍历树形结构,因ParentNode中的TreeModel从未被设置。
目前可通过手动设置属性或频繁调用SaveChanges()规避,但前者违背EF设计初衷,后者不符合文档推荐。请问实体创建的正确工作流是什么?
正确工作流说明
1. 同步双向导航属性
EF的变更跟踪仅在双向导航属性同步设置时,才会自动维护内存中的关联关系。添加子实体时,不能只调用集合的Add(),必须同时设置反向导航属性:
// 添加Node按钮修正代码 var parentNode = new ParentNode(); parentNode.TreeModel = tree; // 显式绑定反向引用 tree.ParentNodes.Add(parentNode); new ParentForm(parentNode);
同理,在ParentForm中添加ChildNode时也要同步设置父引用:
// ParentForm内修正代码 var childNode = new ChildNode(); childNode.Parent = currentParentNode; currentParentNode.ChildNodes.Add(childNode);
这并非违背EF设计,而是EF要求双向导航保持一致性——对于新实例化的未跟踪实体,必须手动建立双向关联,才能在内存中形成完整树形结构,无需依赖SaveChanges()生成ID。
2. 利用EF代理自动同步(可选)
如果实体类满足EF代理创建条件(无参构造函数、导航属性为virtual、非密封类),可以用dbContext.Set<T>().Create()替代new实例化实体。代理类会自动同步双向导航:
// 创建TreeModel时用Create() tree = dbContext.Models.Create(); dbContext.Models.Add(tree); // 创建ParentNode时 var parentNode = dbContext.ParentNodes.Create(); tree.ParentNodes.Add(parentNode); // 此时parentNode.TreeModel会被代理自动赋值为tree new ParentForm(parentNode);
3. 延迟统一提交SaveChanges()
无需频繁调用SaveChanges(),可以在用户完成所有表单操作、确认提交时再统一执行。只要内存中双向导航关联正确,整个构建过程中都能自由遍历树形结构,访问任意层级的实体属性。
4. 明确变更跟踪的生效范围
EF的自动同步仅针对已被上下文跟踪的实体(比如从数据库加载的实体)。对于全新实例化的实体,因为尚未完全纳入上下文的跟踪体系,必须手动建立双向关联,否则内存中的导航属性会保持null。
内容的提问来源于stack exchange,提问作者Jeroen3

