53

I am creating software where user can create new product based on older product.

Now I need to make copying / cloning operations with Entity Framework. First I started writing like this:

foreach(sourcedata1 in table1)
{
   ... create new table
   ... copy data
   ... create Guid
   ... add
   foreach(sourcedata2 in table2)
   {
       ... create new table
       ... copy data
       ... create Guid
       ... add       

       ... and so on
   }
}

Problem is that this not a nice way to do it. Is there any easy way clone information (except Guid that needs to be generated for new rows) or should I manually copy everything?

Other solution

You could also use EmitMapper or AutoMapper to do copying of the properties.

Tx3
  • 6,796
  • 4
  • 37
  • 52

5 Answers5

66

To clone an Entity in Entity Framework you could simply Detach the entity from the DataContext and then re-add it to the EntityCollection.

context.Detach(entity);
entityCollection.Add(entity);

Update for EF6+ (from comments)

context.Entry(entity).State = EntityState.Detached;
entity.id = 0;
entity.property = value;
context.Entry(entity).State = EntityState.Added;
context.SaveChanges();
freedomn-m
  • 27,664
  • 8
  • 35
  • 57
ChrisNel52
  • 14,655
  • 3
  • 30
  • 36
  • 3
    For those trying this method. Remember you won't get a new Id (key value) until you call context.SaveChanges() – Rabbi Jan 09 '11 at 16:18
  • 25
    The only problem with this is if you detach an entity, you lose all the references. – Martin Aug 31 '11 at 13:13
  • 10
    Note: if you detach an entity, it will NOT detach related entities, so if you do var entity = context.MyEntities.Include("MyRelated").First(t => t.ID == 1) followed by Detach(entity), MyRelated will still be in context, meaning they will get inadvertently deleted when you call SaveChanges(). – John Zumbrum Sep 24 '13 at 19:45
  • @JohnZ And that's because the Lazy Loading. For who don't know it, EF creates dynamic proxies in order to load data "on demand". When you use Include() basically you are doing just one query with all the data and EF populate all the specified (navigation) properties with it. When you detach an entity, basically you are saying: "Hey EF, get the f!ck out of my object, don't override my properties.". So if dynamic proxies gone, lazy loading is gone too. – Emanuel Gianico Aug 18 '14 at 05:58
  • .Detach() doesn't exist in EF5, is this an EF6 method? – Rocklan Apr 14 '15 at 00:16
  • 1
    With this approach, would `.AsNoTracking()` in a linq query help? (so you wouldn't need to detach the entity yourself)? – Jon Onstott Jul 24 '15 at 07:28
  • 7
    In EF5+ I did this `context.Entry(entity).State = EntityState.Detached;` – John Oct 17 '15 at 09:05
10
public static EntityObject Clone(this EntityObject Entity)
{
    var Type = Entity.GetType();
    var Clone = Activator.CreateInstance(Type);

    foreach (var Property in Type.GetProperties(BindingFlags.GetProperty | BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly | BindingFlags.SetProperty))
    {
        if (Property.PropertyType.IsGenericType && Property.PropertyType.GetGenericTypeDefinition() == typeof(EntityReference<>)) continue;
        if (Property.PropertyType.IsGenericType && Property.PropertyType.GetGenericTypeDefinition() == typeof(EntityCollection<>)) continue;
        if (Property.PropertyType.IsSubclassOf(typeof(EntityObject)))  continue;

        if (Property.CanWrite)
        {
            Property.SetValue(Clone, Property.GetValue(Entity, null), null);
        }
    }

    return (EntityObject)Clone;
}

This is a simple method I wrote. It should work for most people.

SLaks
  • 868,454
  • 176
  • 1,908
  • 1,964
Tomasi
  • 2,517
  • 6
  • 31
  • 43
  • This works really well for modal dialogs. You clone the object, set it as the context for the modal dialog and then when they click ok set the values back to the original object. If they cancel you do nothing. Very easy. Previously I was undoing the changes but this got messy if the object already had changes so it would get undone right to the start. This also solves the problem that the binding on the main window would update as the user typed in the dialog box. – MikeKulls Aug 30 '11 at 07:41
  • 5
    This needs a number of fixes. The first is I think you meant to use continue instead of break for the the first three lines in the foreach statement. Depending on the order of the properties returned this will behave incorrectly. The second is that you need to remove BindingFlags.DeclaredOnly as this causes it to fail with entity objects which are inherited from other entity objects. Eg, in my case Staff is inherited from Person but I was just getting the properties on Staff cloned. – MikeKulls Aug 31 '11 at 07:08
  • This also loses the references? – Paul Smith Oct 15 '12 at 13:13
8

To add a new row whose content is based on an existing row, follow these steps:

  1. Get an entity based on the starting row.
  2. Set the entry state for the entity to Added.
  3. Modify the entity.
  4. Save changes.

Here's an example:

var rabbit = db.Rabbits.First(r => r.Name == "Hopper");
db.Entry(rabbit).State = EntityState.Added;
rabbit.IsFlop = false;
db.SaveChanges();
Edward Brey
  • 40,302
  • 20
  • 199
  • 253
  • When I tried this I got an error message that said "The changes to the database were committed successfully, but an error occurred while updating the object context." – Rocklan Apr 14 '15 at 00:15
0

If you want to create a copy of an entity for comparison later in your code execution, you can select the entity in a new db context.

If for example you are updating an entity, then later in the code you want to compare the updated and original entity:

var db = new dbEntityContext();
var dbOrig = new dbEntityContext();

var myEntity = db.tblData.FirstOrDefault(t => t.Id == 123);
var myEntityOrig = dbOrig.tblData.FirstOrDefault(t => t.Id == 123);

//Update the entity with changes
myEntity.FirstName = "Gary";

//Save Changes
db.SaveChnages();

At this point, myEntity.FirstName will contain "Gary" whilst myEntityOrig.FirstName will contain the original value. Useful if you have a function to log changes where you can pass in the updated and original entity.

SausageFingers
  • 1,796
  • 5
  • 31
  • 52
0

A really short way of duplicating entities using generics (VB, sorry).
It copies foreign key values (external IDs) but doesn't load their related objects.

<Extension> _
Public Function DuplicateEntity(Of T As {New, Class})(ctx As myContext, ent As T) As T
    Dim other As New T 'T is a proxy type, but New T creates a non proxy instance
    ctx.Entry(other).State = EntityState.Added 'attaches it to ctx
    ctx.Entry(other).CurrentValues.SetValues(ent) 'copies primitive properties
    Return other
End Function

For instance:

newDad = ctx.DuplicateEntity(oDad)
newDad.RIDGrandpa ' int value copied
newDad.Grandpa    ' object for RIDGrandpa above, equals Nothing(null)
newDad.Children   ' nothing, empty

I don't know exactly how to reload Grandpa in this case.
This doesn't work:

ctx.SaveChanges()
ctx.Entry(newDad).Reload()

but really, no problem. I would rather assign Grandpa by hand if I need it.

newDad.Grandpa = oDad.Grandpa

EDIT: As MattW proposes in his comment, detaching and finding the new entity you get its children loaded (not collections).

ctx.Entry(newDad).State = EntityState.Detached
ctx.Find(newDad.RowId) 'you have to know the key name
Community
  • 1
  • 1
Ivan Ferrer Villa
  • 2,129
  • 1
  • 26
  • 23
  • Perfect... I was just writing a Web API Put method and wanted to check that a few key fields weren't being altered from existing values - I figured load the entity from the database, compare the immutable fields to the value from Web API, copy the rest over and save the database copy. SetValues is just what I was looking for to do the "copy the rest over" step. – MattW Jan 26 '16 at 21:26
  • 1
    For the reload problem, did you try a `Detach` followed by a `Find`? I expect it would give a new object reference, don't know if that would be a problem for you. – MattW Jan 26 '16 at 21:30