Create shallow copy / clone of EntityObject subclass
We have an audit table in our database and when updated, the old and new values ββare serialized to XML and stored in one row. Objects are currently deeply cloned, thus:
public EntityObject CloneEntity(EntityObject obj)
{
DataContractSerializer dcSer = new DataContractSerializer(obj.GetType());
MemoryStream memoryStream = new MemoryStream();
dcSer.WriteObject(memoryStream, obj);
memoryStream.Position = 0;
EntityObject newObject = (EntityObject)dcSer.ReadObject(memoryStream);
return newObject;
}
While it works, it generates a huge amount of data due to related records extracted from a deep clone, with hundreds of thousands of reads from the DB per dcSer.WriteObject(memoryStream, obj)
, and the possible size of a MemoryStream is around 200MB, not to mention the amount of data returned to the DB. Not ideal.
So, I would like to make a member clone instead, since I understand that the clone member left object references and did not copy all of the Entity Framework models associated with it.
So, I did this:
public EntityObject CloneEntity(EntityObject obj)
{
EntityObjectAuditable auditable = (EntityObjectAuditable)obj; // invalid cast exception
return auditable.ShallowCopy();
}
// ....
public class EntityObjectAuditable : EntityObject
{
public EntityObjectAuditable ShallowCopy()
{
return (EntityObjectAuditable)this.MemberwiseClone();
}
}
but I am getting an invalid throwing exception because the actual type of the incoming EntityObject
is a subclass that belongs to the table itself.
I have also tried using an extension method to access MemberwiseClone()
, but the extension methods cannot access protected methods.
So how can I create a shallow copy of a generic EntityObject?
source to share
My first recommendation is to try this, which is similar to what you are doing now but worked for me with very little overhead:
DataContractSerializationUtils.SerializeToXmlString(Entity, throwExceptions);
Also, I have used this method with success and do not find the output to be too verbose. This is similar to what you are doing now.
/// <summary>
/// Creates an exact duplicate of the entity provided
/// </summary>
/// <param name="source">The source copy of the entity</param>
/// <returns>An exact duplicate entity</returns>
public TEntity Clone(TEntity Source)
{
// Donβt serialize a null object, simply return the default for that object
if (ReferenceEquals(Source, null))
{
return default(TEntity);
}
var dcs = new DataContractSerializer(typeof (TEntity));
using (Stream stream = new MemoryStream())
{
dcs.WriteObject(stream, Source);
stream.Seek(0, SeekOrigin.Begin);
return (TEntity) dcs.ReadObject(stream);
}
}
If none of these options seem appealing, I recommend writing a short function that uses reflection to copy any properties from the original object.
source to share
From:
http://www.codeproject.com/Tips/474296/Clone-an-Entity-in-Entity-Framework-4 .
It's much more efficient and faster than serialization - exactly what you're looking for! It basically uses reflection to copy the required properties into a new EntityObject of the same type, and can do this in any EntityObject-derived class using generics.
public static T CopyEntity<T>(MyContext ctx, T entity, bool copyKeys = false) where T : EntityObject
{
T clone = ctx.CreateObject<T>();
PropertyInfo[] pis = entity.GetType().GetProperties();
foreach (PropertyInfo pi in pis)
{
EdmScalarPropertyAttribute[] attrs = (EdmScalarPropertyAttribute[])pi.GetCustomAttributes(typeof(EdmScalarPropertyAttribute), false);
foreach (EdmScalarPropertyAttribute attr in attrs)
{
if (!copyKeys && attr.EntityKeyProperty)
continue;
pi.SetValue(clone, pi.GetValue(entity, null), null);
}
}
return clone;
}
For example, you have a company: Customer, which has a navigation property: Orders. Then you can copy the Customer and their Orders using the above method as follows:
Customer newCustomer = CopyEntity(myObjectContext, myCustomer, false);
foreach(Order order in myCustomer.Orders)
{
Order newOrder = CopyEntity(myObjectContext, order, true);
newCustomer.Orders.Add(newOrder);
}
source to share