【问题标题】:Entity Framework: CRUD on objects with complex relationships实体框架:对具有复杂关系的对象进行 CRUD
【发布时间】:2011-07-11 15:08:09
【问题描述】:

我的系统中有很多对象都继承自基类Entity。这些对象有很多关系,包括一对一、一对多和多对多。由于我使用的是 WCF,因此我的 DbContext 对象与每个 CRUD 调用都断开连接。这导致我在对具有关系的对象进行基本 CRUD 操作时遇到了一些问题。

例如,我有一个具有基本父子关系的对象。我称之为节点。

[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
  }
}

问题是,无论我怎么做,Entity Framework 都会感到困惑。

当我使用基本的 DbContext.Nodes.Add 操作时,它会在我的测试用例与现有孩子的情况下搞混。它在数据库中创建一个现有子项的重复条目,然后为这个新子项提供正确的ParentID。如果我遍历孩子并首先在孩子身上使用DbContext.Nodes.Add,也会发生这种情况。

我尝试循环遍历所有子级并在所有子级上使用DbContext.Nodes.Attach,然后在父级上使用DbContext.Nodes.Add,但这会导致我的新子测试用例出现异常。

System.Data.Entity.Infrastructure.DbUpdateConcurrencyException:存储 更新、插入或删除语句影响了意外数量的 行 (0)。实体可能已被修改或删除,因为实体 被加载。刷新 ObjectStateManager 条目。 ...

更不用说我担心这会如何工作,例如,如果您添加一个带有孩子的节点和一个带有孩子的孩子等等。我希望我的 CRUD 方法对所有可能的有效对象构造做出适当的反应。

根据我能够找到的研究,归结为 EF 不适合或不擅长这种事情,最好自己管理关系。这是真的?如果是这样,有没有我可以效仿的例子?有什么小窍门之类的吗?

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

【问题讨论】:

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


    【解决方案1】:

    您必须为现有的子节点附加,而不是为新的子节点附加。假设您可以通过查看它们的ID(ID > 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 不必更改已经在上下文中的对象的状态。在性能方面可能会更好,但我不确定。可能不重要。

    【讨论】:

    • 这行得通,但是如果我有一个有一个孩子的节点怎么办?我应该做一个while (childNode.Children != null) 还是什么?
    • @OpticalDelusion:我已经在答案中添加了一个编辑。不确定它是否有效,但值得一试。
    • 是的,我打算只用一个while循环而不是递归来做到这一点。感谢您的意见。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-03-29
    • 1970-01-01
    • 1970-01-01
    • 2016-07-17
    相关资源
    最近更新 更多