I am using Entity Framework 5 (DBContext
) and I am trying to find the best way to deep copy an entity (i.e. copy the entity and all related objects) and then save the new entities in the database. How can I do this? I have looked into using extension methods such as CloneHelper
but I am not sure if it applies to DBContext
.
One cheap easy way of cloning an entity is to do something like this:
var originalEntity = Context.MySet.AsNoTracking()
.FirstOrDefault(e => e.Id == 1);
Context.MySet.Add(originalEntity);
Context.SaveChanges();
the trick here is AsNoTracking() - when you load an entity like this, your context do not know about it and when you call SaveChanges, it will treat it like a new entity.
If MySet
has a reference to MyProperty
and you want a copy of it too, just use an Include
:
var originalEntity = Context.MySet.Include("MyProperty")
.AsNoTracking()
.FirstOrDefault(e => e.Id == 1);
Here's another option.
I prefer it in some cases because it does not require you to run a query specifically to get data to be cloned. You can use this method to create clones of entities you've already obtained from the database.
//Get entity to be cloned
var source = Context.ExampleRows.FirstOrDefault();
//Create and add clone object to context before setting its values
var clone = new ExampleRow();
Context.ExampleRows.Add(clone);
//Copy values from source to clone
var sourceValues = Context.Entry(source).CurrentValues;
Context.Entry(clone).CurrentValues.SetValues(sourceValues);
//Change values of the copied entity
clone.ExampleProperty = "New Value";
//Insert clone with changes into database
Context.SaveChanges();
This method copies the current values from the source to a new row that has been added.
This is a generic extension method which allows generic cloning.
You have to fetch System.Linq.Dynamic
from nuget.
public TEntity Clone<TEntity>(this DbContext context, TEntity entity) where TEntity : class
{
var keyName = GetKeyName<TEntity>();
var keyValue = context.Entry(entity).Property(keyName).CurrentValue;
var keyType = typeof(TEntity).GetProperty(keyName, System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance).PropertyType;
var dbSet = context.Set<TEntity>();
var newEntity = dbSet
.Where(keyName + " = @0", keyValue)
.AsNoTracking()
.Single();
context.Entry(newEntity).Property(keyName).CurrentValue = keyType.GetDefault();
context.Add(newEntity);
return newEntity;
}
The only thing you have to implement yourself is the GetKeyName method. This could be anything from return typeof(TEntity).Name + "Id"
to return the first guid property
or return the first property marked with DatabaseGenerated(DatabaseGeneratedOption.Identity)]
.
In my case I already marked my classes with [DataServiceKeyAttribute("EntityId")]
private string GetKeyName<TEntity>() where TEntity : class
{
return ((DataServiceKeyAttribute)typeof(TEntity)
.GetCustomAttributes(typeof(DataServiceKeyAttribute), true).First())
.KeyNames.Single();
}
I had the same issue in Entity Framework Core where deep clone involves multiple steps when children entities are lazy loaded. One way to clone the whole structure is the following:
var clonedItem = Context.Parent.AsNoTracking()
.Include(u => u.Child1)
.Include(u => u.Child2)
// deep includes might go here (see ThenInclude)
.FirstOrDefault(u => u.ParentId == parentId);
// remove old id from parent
clonedItem.ParentId = 0;
// remove old ids from children
clonedItem.Parent1.ForEach(x =>
{
x.Child1Id = 0;
x.ParentId= 0;
});
clonedItem.Parent2.ForEach(x =>
{
x.Child2Id = 0;
x.ParentId= 0;
});
// customize entities before inserting it
// mark everything for insert
Context.Parent.Add(clonedItem);
// save everything in one single transaction
Context.SaveChanges();
Of course, there are ways to make generic functions to eager load everything and/or reset values for all keys, but this should make all the steps much clear and customizable (e.g. all for some children to not be cloned at all, by skipping their Include).
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With