实体框架:具有复杂关系的对象的CRUD

时间:2011-07-11 15:08:09

标签: c# wcf entity-framework code-first

我的系统中有很多对象都是从基类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不仅仅意味着或者只是对这种事情不好,而且最好是自己管理关系。这是真的?如果是这样,我可以效仿一个例子吗?任何提示等?

我开始使用一种方法来自己处理使用反射的关系,但我觉得这只是一种解决应该是基本问题的荒谬方式。

1 个答案:

答案 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);
        }
    }
}

我认为同时调用AttachAdd非常重要(与上面的简单示例相反),因为当您将一个节点附加到上下文时,所有子节点和孙子节点也会被附加。 (这意味着它们现在处于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不必更改已经在上下文中的对象的状态。这可能是更好的性能,但我不确定。可能无论走哪条路都没关系。