How to deep copy an entity

2019-03-15 18:58发布

问题:

I found this snippet here:

public static T DeepClone<T>(this T obj)
    {
        using (var ms = new MemoryStream()) {
            var bf = new BinaryFormatter();
            bf.Serialize(ms, obj);
            ms.Position = 0;
            return (T)bf.Deserialize(ms);
        }
    }

Which says that we can do deep copy of all related objects through this thing.

I'm trying to do a copy like this:

db.Detach(myEntity); 
myEntity.EntityKEy = null;
Entity newEntity = new Entity();
newEntity = DeepClone<Entity>(Entity);
db.Entities.AddObject(newEntity);
db.SaveChanges();

IT works, but still does not copy any nested\related records. what do I do wrong here?

I have this structure Entity->ChildEntity ->ChildChildEntity
-> - one-to-many
so I assume when I copy entity it will also copy all child records.

UPDATE: After suggestions, I did this:

Entity newEntity = new Entity();
Eneity Entity = db.Include("ChildEntity").Where(p=>p.Id==Id).Single();
newEntity = DeepClone<Entity>(Entity);
db.Detach(myEntity); 
myEntity.EntityKEy = null;
db.Entities.AddObject(newEntity);
db.SaveChanges();

Getting exception on AddObject line:

An object with the same key already exists in the ObjectStateManager. The ObjectStateManager cannot track multiple objects with the same key.

回答1:

The important point is that you must load related entities and create deep clone prior to detaching. If you detach the entity all relations are silently removed because Detach method works only for single entity and entity graph cannot consists of both attached and detached entities. That is a reason why you need serialization instead of simply calling Detach.

Don't forget to turn off lazy loading otherwise your serialization will pull data for other navigation properties from database as well. Also remember that this deep copy will create new version of all entities in the graph so adding the root entity will add all related entities as well.



回答2:

The EntityKeys for all the child objects get cloned too, so you need to set each child's EntityKey to null before trying to add them with AddObject.

Entity oldEntity = db.Include("ChildEntity").Where(p => p.Id == Id).Single();
Entity newEntity = oldEntity.DeepClone(); // assuming you've put your DeepClone extension method in a static class so that it can be used as an extension
newEntity.EntityKey = null;
foreach(var childEntity in newEntity.ChildEntities)
{
    childEntity.EntityKey = null;
}
db.Entities.AddObject(newEntity);
db.SaveChanges();


回答3:

If you haven't loaded the child entities before detaching the entity, they will not be serialized. Make sure all those navigational properties you want to deep clone are loaded before you detach the entity.

Edit

Eager load the navigational properties that must be serialized

var entity = db.Entities.Include("ChildEntity.ChildChildEntity")
        .Where(l=>l.ID == myId).Single();


回答4:

You maybe should save context before you try to attach the entity one more time

Entity newEntity = new Entity();   
Eneity Entity = db.Include("ChildEntity").Where(p=>p.Id==Id).Single();   
newEntity = DeepClone<Entity>(Entity);   
db.Detach(myEntity);    
db.SaveChanges();  
myEntity.EntityKEy = null;   
db.Entities.AddObject(newEntity);   
db.SaveChanges();