3

I want to create an integration test which grabs an EF entity from the DB, clones it to a detached object, modifies it and then saves it back and compares it again to the original.

However, I was using AutoMapper to create the clone of the class, but it turns out this is also tracked or an alias to the original object. I need it to be completely detached from EF, and am able to do this outside of my repository class (i.e. not using any EF detach methods).

The reason for doing this is my EF class contains nested collections of other classes and EF doesn't handle persisting the whole object tree. Hence, my Update() method in my repository class handles this and I want my NUnit test to test this code. I want the test is to be able to quickly create a copy of my original class without EF tracking it.

Ladislav Mrnka
  • 360,892
  • 59
  • 660
  • 670
jaffa
  • 26,770
  • 50
  • 178
  • 289
  • I think this is something similar, it is an mapping test for EF Code First Entities: http://valueinjecter.codeplex.com/wikipage?title=Automatic%20mapping%20tests%20for%20EF4%20Code%20First%20Entities&referringTitle=Home, although it's not automapper – Omu Apr 08 '11 at 17:47

3 Answers3

6

Creating a cloned object containing current, original, or database values The DbPropertyValues object returned from CurrentValues, OriginalValues, or GetDatabaseValues can be used to create a clone of the entity. This clone will contain the property values from the DbPropertyValues object used to create it. For example:

using (var context = new UnicornsContext())
{
    var unicorn = context.Unicorns.Find(1);

    var clonedUnicorn = context.Entry(unicorn).GetDatabaseValues().ToObject();
}

Note that the object returned is not the entity and is not being tracked by the context. The returned object also does not have any relationships set to other objects.

The cloned object can be useful for resolving issues related to concurrent updates to the database, especially where a UI that involves data binding to objects of a certain type is being used. (See Part 9 for more details on dealing with optimistic concurrency.)

*From http://blogs.msdn.com/b/adonet/archive/2011/01/30/using-dbcontext-in-ef-feature-ctp5-part-5-working-with-property-values.aspx

Hope it can helps others

VinnyG
  • 6,883
  • 7
  • 58
  • 76
  • Hi, thanks for the code, I might try this when I come across this scenario in the future. – jaffa Nov 17 '11 at 14:31
  • 1
    Thanks a lot for this piece of code. I used it to copy/clone an EF 5 entity. When the entity is added to the DB with context.MyEntity.Add(clonedEntity) it also preserves the relationships as long as they are exposed in the Model as Ids (I included FK-Ids when generating the Model). – LukeSolar Feb 06 '13 at 17:22
  • Also some approach how to include loaded related entities would be useful. – Vojtěch Dohnal Apr 09 '15 at 09:27
4

All troubles are gone once you're using EF 5+ where they introduced AsNoTracking() method. The line below returns an unlinked instance, so all the context won't be aware about any changes in that instance:

context.Clients.AsNoTracking().FirstOrDefault(item => item.Id == id);

If Clients has a reference to Address and you want an unlinked instance of it too, just use an Include:

context.Clients
       .Include("Address").AsNoTracking()
       .FirstOrDefault(item => item.Id == id);
Alex Klaus
  • 8,168
  • 8
  • 71
  • 87
0

If it is a test you can do anything and you don't have to be binded to any architectural approach like repository. Your repository probably receive context as injection so you can have access to it. Another point is that I don't believe that AutoMapper will create tracked entity.

The one way to make a copy of the class is using serialization which by default saves only public fields (Xml serialization or DataContract serialization). Serialize the object and deserialize it back to a new instance. Serialization will save the whole object graph and deserialized object graph will be detached. Just be aware that that those serializations don't likes cyclic references in object graph (navigation property from A to B and from B to A from cycles). Serialization is also too much aggresive so it can traverse the graph more deeply then you want - this can be especially dangerous in many to many relations.

The best approach is using either ICloneable interface and implement Clone or define support methods which will do different clones with required depth.

Here is another approach for clonning EntityObject based entities. It is tough code, especially part with Reflection.Emit. But this will not help you because code-first is using POCOs.

Ladislav Mrnka
  • 360,892
  • 59
  • 660
  • 670