我的系统中有很多对象都是从基类Entity继承而来的。这些对象有很多关系,包括一对一,一对多和多对多。由于我使用的是WCF,因此我的DbContext对象与每个CRUD调用都会断开连接。这导致我遇到关于具有关系的对象的基本CRUD操作的一些问题。
例如,我有一个具有基本父子关系的对象。我将其称为Node。
[DataContract(IsReference=true)]
public partial class Node : Entity
{
[DataMember]
public long ID { get; private set; }
[DataMember]
public long? ParentID { get; set; }
[DataMember]
public List<Node> Children { get; set; }
}
我希望能够添加一个已存在子节点的新节点,并添加一个尚未存在子节点的节点。
// Node with a new child node
Node nodeWithNewChild = new Node()
{
Children = new List<Node>()
{
new Node()
}
}
// A pre-existing child node
Node existingChildNode = new Node();
// Node with a pre-existing child node
Node nodeWithExistingChild = new Node()
{
Children = new List<Node>()
{
existingChildNode
}
}
问题在于,无论我如何处理它,实体框架都会感到困惑。
当我使用基本的DbContext.Nodes.Add
操作时,它会影响我对现有孩子的测试用例。它在数据库中创建现有子项的重复条目,然后为这个新子项提供正确的ParentID
。如果我遍历孩子并首先对孩子使用DbContext.Nodes.Add
,也会发生这种情况。
我尝试循环遍历所有孩子并在所有孩子身上使用DbContext.Nodes.Attach
,然后在父级上使用DbContext.Nodes.Add
,但这会在我的测试用例中导致一个新孩子出现异常。
System.Data.Entity.Infrastructure.DbUpdateConcurrencyException:存储 更新,插入或删除语句会影响意外的数量 行(0)。自实体以来,实体可能已被修改或删除 装了。刷新ObjectStateManager条目。 ...
更不用说我担心这会如何起作用,例如,如果你添加了一个带有孩子的孩子的节点,等等。我希望我的CRUD方法对所有可能的有效对象构造做出适当的反应。
从我能够发现的研究中可以看出,EF不仅仅意味着或者只是对这种事情不好,而且最好是自己管理关系。这是真的?如果是这样,我可以效仿一个例子吗?任何提示等?
我开始使用一种方法来自己处理使用反射的关系,但我觉得这只是一种解决应该是基本问题的荒谬方式。
答案 0 :(得分:1)
您必须附加现有子节点,而不是附加新子节点。假设你可以通过查看他们的ID
(ID&gt; 0表示:现有)来区分新节点和现有节点,那么这可能是这样的:
if (childNode.ID > 0)
context.Nodes.Attach(childNode);
Node newParentNode = new Node()
{
Children = new List<Node>()
{
childNode
}
};
context.Nodes.Add(newParentNode);
context.SaveChanges();
如果ID == 0,childNode
也将插入到数据库中。否则(由于附加到上下文)将不会创建新的子节点记录。
修改强>
如果您有一个包含子节点和孙子节点的复杂图表,其中可以包含现有节点和新节点的混合,您可以通过以下代码替换上面代码中的两个第一行:
AttachOrAddChildren(childNode);
并添加以下方法:
void AttachOrAddChildren(Node node)
{
if (node.Children != null)
{
foreach(var child in node.Children)
{
if (child.ID > 0)
context.Nodes.Attach(child);
else
context.Nodes.Add(child);
AttachOrAddChildren(child);
}
}
}
我认为同时调用Attach
和Add
非常重要(与上面的简单示例相反),因为当您将一个节点附加到上下文时,所有子节点和孙子节点也会被附加。 (这意味着它们现在处于Unchanged
状态,EF会将节点下的整个子图视为现有对象。)因此,必须将状态设置为Added
显式(通过调用Add
)当循环到达树中的下一级并且有一个新的子节点(ID == 0)时。这同样适用于调用Add
,因此如果节点存在,您必须再次将子节点的状态重置为Unchanged
。
编辑2:
也许在循环中首先调用AttachOrAddChildren
更聪明:
//...
foreach(var child in node.Children)
{
AttachOrAddChildren(child);
if (child.ID > 0)
context.Nodes.Attach(child);
else
context.Nodes.Add(child);
}
//...
这样,树将从叶子遍历到根,EF不必更改已经在上下文中的对象的状态。这可能是更好的性能,但我不确定。可能无论走哪条路都没关系。