Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Entity Framework 6 deep copy/clone of an entity with dynamic depth

I am trying to deep clone/copy an entity Item which contains Children-Items of the same type. The Item has also Parameters, which should be cloned as well. The ItemType, however, should be left as a reference to the existing ItemType.

Diagram for illustration: enter image description here

With the help of Stackoverflow (Entity Framework 5 deep copy/clone of an entity) I have come up with the following rather lousy attempt:

public Item DeepCloneItem(Item item)
{
    Item itemClone = db.Items //Level 1
        .Include(i => i.ChildrenItems.Select(c => c.ChildrenItems )) //3 Levels
        .Include(i => i.Parameters) //Level 1 Params
        .Include(i => i.ChildrenItems.Select(c => c.Parameters)) //Level 2 Params
        .Include(i => i.ChildrenItems.Select(c => c.ChildrenItems
            .Select(cc => cc.Parameters))) //Level 3 Params
        .AsNoTracking()
        .FirstOrDefault(i => i.ItemID == item.ItemID);
    db.Items.Add(itemClone);
    db.SaveChanges();
    return itemClone;
}

For a fixed depth-level of 3 this attempt works like a charm. However, as you can see, this isn't getting pretty nice with each deeper level. The design allows for an infinite number of nesting (In my context, however, there shouldn't be more than 5 levels).

Is there any possibility to dynamically add Includes to the IQueryable depending on the maximal depth?

This is the Item-entity to clone:

public class Item
{
    public int ItemID { get; set; }

    public int? ParentItemID { get; set; }
    [ForeignKey("ParentItemID")]
    public virtual Item ParentItem { get; set; }
    public virtual ICollection<Item> ChildrenItems { get; set; }

    [InverseProperty("Item")]
    public virtual ICollection<Parameter> Parameters { get; set; }

    public ItemTypeIds ItemTypeID { get; set; }
    [ForeignKey("ItemTypeID")]
    public virtual ItemType ItemType { get; set; }
}
like image 522
Luke Avatar asked Nov 23 '15 15:11

Luke


1 Answers

I have found a more generic approach for this problem. For anyone who might encounter a similar problem, here is how I solved it for now:

public Item DeepCloneItem(Item item)
{
    Item itemClone = db.Items.FirstOrDefault(i => i.ItemID == item.ItemID);
    deepClone(itemClone);
    db.SaveChanges();
    return itemClone;
}

private void deepClone(Item itemClone)
{
    foreach (Item child in itemClone.ChildrenItems)
    {
        deepClone(child);
    }
    foreach(Parameter param in itemClone.Parameters)
    {
        db.Entry(param).State = EntityState.Added;
    }
    db.Entry(itemClone).State = EntityState.Added;
}

Keep in mind that the recursive call must be before the EntityState.Added allocation. Othwerwise, the recursion will stop at the second level. Furthermore, the recursive method must called with an Entity in an Attached state. Otherwise, the recursion will stop at the second level as well.

Consider replacing the recursion with an iterative approach if your entity-tree is very deep. For more information, have a look at: Wikipedia Recursion versus iteration

Feedback and improvements welcome!

like image 180
Luke Avatar answered Nov 04 '22 01:11

Luke