实体框架:对具有复杂关系的对象进行CRUD
本文关键字:对象 CRUD 关系 复杂 框架 实体 | 更新日期: 2023-09-27 18:01:41
我的系统中有许多对象都继承自一个基类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:存储更新、插入或删除语句影响了意外数量的自实体以来,实体可能已经被修改或删除被加载。刷新ObjectStateManager表项. ...
更不用说我担心的是如何工作,例如,您添加了一个带有子节点的子节点的子节点的子节点等等。我希望我的CRUD方法对所有可能有效的对象构造作出适当的反应。
从我所能找到的研究来看,这归结为一个事实,EF就是不适合或不擅长这类事情,最好是自己管理关系。这是真的吗?如果有,有没有我可以效仿的例子?有什么建议吗?
我开始使用反射来处理关系,但我觉得这只是一个荒谬的方法来解决应该是一个基本问题。
必须为现有子节点附加,而不能为新子节点附加。假设您可以通过查看它们的ID
(ID> 0表示:existing)来区分新节点和现有节点,那么它可能看起来像这样:
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
也将被插入到DB中。否则(由于附加到上下文)将不会创建新的子节点记录。
如果您有一个包含子女和孙子等的复杂图形,可以包含现有和新节点的混合,您可以将上面代码中的前两行替换为以下内容:
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会将节点下面的整个子图视为现有对象。)因此,当循环到达树的下一层并且有一个新的子节点(ID == 0)时,您必须显式地将状态设置为Added
(通过调用Add
)。调用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将不必改变已经在上下文中的对象的状态。这可能是更好的性能,但我不确定。也许这并不重要。