Question

We have an audit table in our database, and on update the old and new values are serialized to XML and stored in the same row. The objects are currently deep-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;
}

Whilst this works, it generates vast amounts of data due to the related records pulled from the deep clone, with hundreds of thousands of reads from the DB on dcSer.WriteObject(memoryStream, obj), and an eventual MemoryStream size of some 200MB, not to mention the amount of data being written back to the DB. Not ideal.

So I would like to do a memberwise clone instead, as it is my understanding that a memberwise clone would leave the object references out, and avoid copying all the related Entity Framework models.

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 get an invalid cast exception because the actual type of the incoming EntityObject is a subclass relating to the table itself.

I have also tried using an extension method to access MemberwiseClone(), but extension methods cannot access protected methods.

So, how can I create a shallow copy of a generic EntityObject?

Was it helpful?

Solution

My first recommendation would be to try this, which is similar to what you're doing now, but has worked for me with very little overhead:

DataContractSerializationUtils.SerializeToXmlString(Entity, throwExceptions);

Also, I've used this method before with success, and don't find the output too verbose. It seems to be nearly identical to what you're 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 neither of these options seem appealing, my recommendation is to write a short function that uses reflection to copy any properties from the source entity.

OTHER TIPS

From:

http://www.codeproject.com/Tips/474296/Clone-an-Entity-in-Entity-Framework-4.

Its much more efficant and faster then serialization - just what you are looking for! Basically it uses reflection to copy the neccessary properties to a new EntityObject of the same type and is able to do so on any class derived from an EntityObject by making use of 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 say you had an entity: Customer, which had the Navigation Property: Orders. You could then copy the Customer and their Orders using the above method like so:

Customer newCustomer = CopyEntity(myObjectContext, myCustomer, false);

foreach(Order order in myCustomer.Orders)
{
    Order newOrder = CopyEntity(myObjectContext, order, true);
    newCustomer.Orders.Add(newOrder);
}

What about CurrentValues.ToObject() in EF 6?

var shallowCopy = (TEntity)_dbContext.Entry(entity).CurrentValues.ToObject();
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top