3

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?

sennett
  • 8,014
  • 9
  • 46
  • 69
  • I see the question was asked a while ago however I had the exact same problem and came up with a solution so have added that as an answer incase anyone else wants to know! – markmnl Oct 11 '12 at 00:24

3 Answers3

3

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);
}
markmnl
  • 11,116
  • 8
  • 73
  • 109
3

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.

msigman
  • 4,474
  • 2
  • 20
  • 32
  • Thanks for your reply. `DataContractSerializationUtils.SerializeToXmlString` seems to be something from the West Wind Web Toolkit which I don't want to include in my console app; the second method suffers from exactly the same problems as what I am doing at the moment; and the third? Urgh. Reflection. It really shouldn't be this hard and fragile to create a shallow clone, especially considering `MemberwiseClone` exists. – sennett Mar 14 '12 at 03:52
  • Blimey I just read my comment there and I come across as a total douche! Sorry about that. Thanks very much for your help. I ended up using reflection, and this answer helped me when instantiating a new instance from a type: http://stackoverflow.com/questions/752/get-a-new-object-instance-from-a-type-in-c-sharp – sennett Mar 14 '12 at 04:11
  • I agree it shouldn't be so hard. Glad to help! – msigman Mar 14 '12 at 11:11
0

What about CurrentValues.ToObject() in EF 6?

var shallowCopy = (TEntity)_dbContext.Entry(entity).CurrentValues.ToObject();