0

My model looks something like this:

Company
-Locations

Locations
-Stores

Stores
-Products

So I want to make a copy of a Company, and all of its associations should also be copied and saved to the database.

How can I do this if I have the Company loaded in memory?

Company company = DbContext.Companies.Find(123);

If it is tricky, I can loop through each association and then call create a new object. The Id's will be different but everything else should be the same.

I am using EF 6.

cool breeze
  • 4,461
  • 5
  • 38
  • 67

2 Answers2

2

Cloning object graphs with EF is a piece of cake:

var company = DbContext.Companies.AsNoTracking()
                       .Include(c => c.Locations
                           .Select(l => l.Stores
                               .Select(s => s.Products)))
                       .Where(c => c.Id == 123)
                       .FirstOrDefault();
DbContext.Companies.Add(company);
DbContext.SaveChanges();

A few things to note here.

  • AsNoTracking() is vital, because the objects you add to the context shouldn't be tracked already.
  • Now if you Add() the company, all entities in its object graph will be marked as Added as well.
  • I assume that the database generates new primary key values (identity columns). If so, EF will ignore the current values from the existing objects in the database. If not, you'll have to traverse the object graph and assign new values yourself.

One caveat: this only works well if the associations are 1:0..n. If there is a n:m association, identical entities may get inserted multiple times. If, for example, Store-Product is n:m and product A occurs at store 1 and store 2, product A will be inserted twice. If you want to prevent this, you should fetch the objects by one context, with tracking (i.e. without AsNoTracking), and Add() them in a new context. By enabling tracking, EF keeps track of identical entities and won't duplicate them. In this case, proxy creation should be disabled, otherwise the entities keep a reference to the context they came from.

More details here: Merge identical databases into one

Community
  • 1
  • 1
Gert Arnold
  • 105,341
  • 31
  • 202
  • 291
  • I am using UnitOfWork, how can I detach correctly? http://www.asp.net/mvc/overview/older-versions/getting-started-with-ef-5-using-mvc-4/implementing-the-repository-and-unit-of-work-patterns-in-an-asp-net-mvc-application – cool breeze Apr 27 '16 at 19:04
  • You don't *have* to use it. Don't let your own architecture strangle you. This generic repository makes it very hard to use EF's features to the fullest. How are you going to use `AsNoTracking` if you want? – Gert Arnold Apr 27 '16 at 21:38
  • But how cannot refactor everything now. I have to change everything then right? – cool breeze Apr 28 '16 at 14:31
  • You should somehow try to get an object graph with AsNoTracking and Add it, or one with proxy creation disabled that you can Add to a new UoW. Your repository methods should be made to allow that. – Gert Arnold Apr 28 '16 at 16:00
0

I would add a method to each model that needs to be cloneable this way, I'd recommend an interface for it also.

It could be done something like this:

//Company.cs
Company DeepClone()
{
    Company clone = new Company();

    clone.Name = this.name;
    //...more properties (be careful when copying reference types)

    clone.Locations = new List<Location>(this.Locations.Select(l => l.DeepClone()));

    return clone;
}

You should repeat this basic pattern for every class and "child" class that needs to be copiable. This way each object is aware of how to create a deep clone of its self, and passes responsibility for child objects off to the child class, neatly encapsulating everything.

It could be used this way:

Company copyOfCompany123 = DbContext.Companies.Find(123).DeepClone;

My apologies if there are any errors in the above code; I don't have Visual Studio available at the moment to verify everything, I'm working from memory.


One other really simple and code efficient way to deeply clone an object using serialization can be found in this post How do you do a deep copy an object in .Net (C# specifically)?

public static T DeepClone<T>(T obj)
{
 using (var ms = new MemoryStream())
 {
   var formatter = new BinaryFormatter();
   formatter.Serialize(ms, obj);
   ms.Position = 0;

   return (T) formatter.Deserialize(ms);
 }
}

Just be aware that this can have some pretty serious resource and performance issues depending on your object structure. Every class that you want to use it on must also be marked with the [Serializable] attribute.

Bradley Uffner
  • 16,641
  • 3
  • 39
  • 76