开发者

Entity Framework: CRUD on objects with complex relationships

I have many objects in my system that all inherit from a base class, Entity. These objects have quite a few relationships, including one-to-one, one-to-many, and many-to-many. Since I am using WCF, my DbContext object is disconnected with every CRUD call. This has caused me to run into some issues with basic CRUD operations on objects with relationships.

For example, I have an object with a basic parent-child relationship. I will call it 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; }

}

I want to be able to add a new node that has a child that already exists and also to add a node that has a child that does not exist yet.

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

The problem is that no matter how I go about it, Entity Framework gets confused.

When I use a basic DbContext.Nodes.Add operation, it messes up on my test case with an existing child. It makes a duplicate entry of the existing child in the database, and then gives this new child the correct ParentID. This also happens if I loop through the children and use DbContext.Nodes.Add on the children first.

I tried looping through all the children and using DbContext.Nodes.Attach on all the children and then using DbContext.Nodes.Add on the parent, but this causes an exception on my test case with a new child.

System.Data.Entity.Infrastructure.DbUpdateConcurrencyException: Store update, insert, or delete statement affected an unexpected number of rows (0). Entities may have been modified or deleted since entities were loaded. Refresh ObjectStateManager entries. ...

Not to mention I am worried about how this would work if, for example, you added a node with a child with a child with a child and so on. I want my CRUD methods to react appropriately to all possible valid object constructions.

From the research I have been able to find, it comes to down to the fact that EF is just not meant for or is just bad at this kind of thing, and it is best to manage relationships myself. Is this true? If so, is there an example I can follow? Any tips, etc.?

I started on a method that would handle relationships myself that uses re开发者_如何转开发flection, but I feel like this is just a ridiculous way to solve what should be a basic problem.


You must attach for existing child nodes and not attach for new child nodes. Let's say you could distinguish between new and existing nodes by looking at their ID (ID > 0 means: existing) then this could look like this:

if (childNode.ID > 0)
    context.Nodes.Attach(childNode);

Node newParentNode = new Node()
{
    Children = new List<Node>()
    {
        childNode
    }
};

context.Nodes.Add(newParentNode);
context.SaveChanges();

In case ID == 0 the childNode will also be inserted into the DB. Otherwise (due to attaching to the context) no new child node record will be created.

Edit:

If you have a complex graph of children and grandchildren and so on which can contain a mix of existing and new nodes you could replace the two first lines in the code above by the following:

AttachOrAddChildren(childNode);

And add the following method:

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

I believe that calling both Attach and Add is important here (in contrast to the simple example above) because when you attach a node to the context all its children and grandchildren get attached as well. (That means they are in Unchanged state now and EF would consider the whole subgraph below the node as existing objects.) Therefore you must set the state to Added explicitely (by calling Add) when the loop reaches the next level in the tree and when you have a new child node (ID == 0). The same applies to calling Add, so that you must reset the state of child nodes to Unchanged again if the nodes are existing.

Edit 2:

Perhaps calling AttachOrAddChildren first in the loop is smarter:

        //...
        foreach(var child in node.Children)
        {
            AttachOrAddChildren(child);
            if (child.ID > 0)
                context.Nodes.Attach(child);
            else
                context.Nodes.Add(child);
        }
        //...

This way the tree would be traversed from the leaves to the root and EF would not have to change the state of objects which are already in the context. It might be better performance-wise but I am not sure. Possibly it doesn't matter which way around.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜