13

How to copy an entity from one context (inheriting from DbContext) to another?

Everything I have found up to works only for ObjectContext but not for the DbContext or uses DbContext but does not work.

For example, I have found/tried:

  • Uses ObjectContext: CloneHelper on CodeProject
  • Setting LazyLoadingEnabled to false results in not filling the ICollection<> properties (foreign-keys)
  • Setting ProxyCreationEnabled to false results in keeping the ICollection<> properties as null (foreign-keys)
  • Entry<>.State=Detached results in not filling the ICollection<> properties (foreign-keys) if set before attaching the property, or blocks clearing Id if set later on.
  • AsNoTracking() results in an exception (one of the following, depends whether inserting to the remote context first an item from the parent's ICollection<> property or first the parent):
    • Parent: The object in the 'ModelFirstSub_First_Target' role cannot be automatically added to the context because it was retrieved using the NoTracking merge option. Explicitly attach the entity to the ObjectContext before defining the relationship.
    • Item: The object could not be added or attached because its EntityReference has an EntityKey property value that does not match the EntityKey for this object.

I will use it for two purposes:

  1. Copy everything from one database to another (the destination will be a clone of the original; a local copy of a remote database). Ids of objects should be preserved.
  2. Add or update selected entities from one database to another (upstreaming changes in the local cache to the remote origin). Ids of the newly created objects does not need to be persisted.

How to perform that?

EF 6.1.1, .NET 4.5.2, C#

Here is the test code which tries to simulate the second action (upstreaming changes back to the remote database):

var addedFirst = localContext.Firsts.AsNoTracking().Single(m => m.Id == 4);
var updatedFirst = localContext.Firsts.AsNoTracking().Single(m => m.Id == 2);

addedFirst.Items.First().Id = 0;
addedFirst.Items.First().FirstId = 0;
addedFirst.Id = 0;

remoteContext.FirstItems.Add(addedFirst.Items.First());
remoteContext.Firsts.Add(addedFirst);

var originalFirst = remoteContext.Firsts.Single(m => m.Id == 2);
var originalItem = originalFirst.Items.First();
originalItem.Title = updatedFirst.Items.First().Title;

Here is the model:

public class ModelFirstBase
{
    public virtual int Id { get; set; }
    public virtual ICollection<ModelFirstSub> Items { get; set; }
    public virtual string Title { get; set; }
}

public class ModelFirstSub
{
    public virtual int Id { get; set; }
    public virtual int FirstId { get; set; }
    public virtual ModelFirstBase First { get; set; }
    public virtual string Title { get; set; }
}

PS: The properties needs to be kept virtual as the model is shared with the production app which benefits from dynamic proxies.

alik
  • 2,244
  • 3
  • 31
  • 44
  • have you tried the Context detach and attach methods? – phil soady Oct 27 '14 at 21:22
  • 2
    There are no such methods on DbContext. – alik Oct 27 '14 at 21:32
  • Context.Set().Attach(poco) or the Context.Set(type pocoType).Attach(poco) version. A DbContext supports many table types. You are attaching/detaching to one of (n) sets. Not the context directly – phil soady Oct 27 '14 at 21:39
  • and this may prove useful to Context.Entry(poco).State = state; – phil soady Oct 27 '14 at 21:50
  • Are your entities serializable and the entity IDs not auto-generated by the database? If so, you could probably serialize/deserialize them with the DataContractSerializer or XmlSerializer to get deep copies. Then you can use the copies to insert them in the target database. – ajawad987 Oct 29 '14 at 19:27
  • If I try to add just after the code setting the ids, the exception is the same as before (complaining about the EntityKey), but raised on the Attach() line. `localContext.Entry(addedFirst).State = EntityState.Detached;` `remoteContext.Set().Attach(addedFirst);` `remoteContext.Entry(addedFirst).State = EntityState.Added;` @philsoady – alik Oct 30 '14 at 19:27
  • For the current project, I have solved it by going through a business object (basically, I am utilizing the code used to show the entity to the user in a view for editing and to save the changes back to the database). – alik Oct 30 '14 at 19:32

2 Answers2

0

Did you try simply translating the model into the second model:

public class ModelFirstBase
{
public virtual int Id { get; set; }
public virtual ICollection<ModelFirstSub> Items { get; set; }
public virtual string Title { get; set; }
}

public class ModelFirstSub
{
public virtual int Id { get; set; }
public virtual int FirstId { get; set; }
public virtual ModelFirstBase First { get; set; }
public virtual string Title { get; set; }
}

public class ModelTranslator
{
public ModelFirstSub TranslateModelFirstBase(ModelFirstBase entity)
{
//do some error handling and null checks.
var second = new ModelFirstSub(){
                  FirstId = entity.Id,
                  .....
             };
return second;
}
}

public void TransferModels(){ //I haven't thought about disposing stuff, you should.
var firstContext = new FirstContext();
var secondContext = new SecondContext();
foreach (var first in firstContext.ModelFirstBases){
     var second = new ModelTranslator().TranslateModelFirstBase(first);
     secondContext.ModelFirstSubs.Add(second);
}
secondContext.SaveChanges();
}

However do note that EF is not meant for bulk copying of data, this even though should work is not an actual solution. You should think about SqlBulkCopy instead if using Sql server or something equivalent for whatever DB you might be using.

SQLBulkCopy: How does SqlBulkCopy Work

Community
  • 1
  • 1
Sumit Maingi
  • 2,173
  • 3
  • 24
  • 44
  • Translating ModelFirstBase to ModelFirstSub seems strange. Even that, I was hoping in something which does not utilize any conversion between objects (for that I can utilize cloning, business models or some automatic mappers classes). – alik Oct 31 '14 at 09:57
  • You can use auto mappers if you wish, This is an excellent library for the same: http://automapper.org/ . Remember that whatever "auto" maps will use reflection and will not be that performant, so if this is something which runs in background and a few milliseconds (and higher CPU usage) does not matter much, go ahead. If you have seperate classes for entities you cannot avoid translation. You can however go with the same classes and use EntityTypeConfiguration (http://msdn.microsoft.com/en-us/library/gg696117(v=vs.113).aspx) for properties that do not match. – Sumit Maingi Oct 31 '14 at 10:42
0

Have you considered a 3rd party library such as AutoMapper or ValueInjector?

It seems that they were built to tackle these types of challenges.

You can easily perform deep cloning, but you will have to configure EF for identity inserts if you want to preserve identity column values.

Community
  • 1
  • 1