Entity Framework 6: Clone object except ID

73,802

Solution 1

I noticed that there is no need for copying. Apparently when adding an instance of a model to the database (even if the ID is set to one that already exists in the database), Entity Framework inserts a new row in the database and auto-increments it's primary key. So this functionality is already built-in into EF. I didn't know this, sorry.
Just for clarity's sake here is an example:

using(var database = new MyDbContext()) {
    MyModel myModel = database.FirstOrDefault(m => m.SomeProperty == someValue);
    myModel.SomeOtherProperty = someOtherValue; //user changed a value
    database.MyModels.Add(myModel); //even though the ID of myModel exists in the database, it gets added as a new row and the ID gets auto-incremented 
    database.SaveChanges();
}

Solution 2

Lori Peterson has suggested using .AsNoTracking() to perform cloning in EF6. I'm using this method and can confirm that it works. You can even include child objects.

var entity = context.Entities
                    .AsNoTracking()
                    .Include(x => x.ChildEntities)
                    .FirstOrDefault(x => x.EntityId == entityId);

entity.SomeProperty = DateTime.Now;

context.Entities.Add(entity);
context.SaveChanges();

When you are retrieving an entity or entities from a dataset, you can tell Entity Framework not to track any of the changes that you are making to that object and then add that entity as a new entity to the dataset. With using .AsNoTracking, the context doesn’t know anything about the existing entity.

Solution 3

When using ObjectContext the answer provided by QuantumHive does not work.

The error returned in that situation is :

An object with the same key already exists in the ObjectStateManager. The ObjectStateManager cannot track multiple objects with the same key.
System.InvalidOperationException: An object with the same key already exists in the ObjectStateManager. The ObjectStateManager cannot track multiple objects with the same key.
   at System.Data.Objects.ObjectStateManager.AddEntry(IEntityWrapper wrappedObject, EntityKey passedKey, EntitySet entitySet, String argumentName, Boolean isAdded)
   at System.Data.Objects.ObjectContext.AddSingleObject(EntitySet entitySet, IEntityWrapper wrappedEntity, String argumentName)
   at System.Data.Objects.DataClasses.RelatedEnd.AddEntityToObjectStateManager(IEntityWrapper wrappedEntity, Boolean doAttach)
   at System.Data.Objects.DataClasses.RelatedEnd.AddGraphToObjectStateManager(IEntityWrapper wrappedEntity, Boolean relationshipAlreadyExists, Boolean addRelationshipAsUnchanged, Boolean doAttach)
   at System.Data.Objects.DataClasses.RelatedEnd.Add(IEntityWrapper wrappedTarget, Boolean applyConstraints, Boolean addRelationshipAsUnchanged, Boolean relationshipAlreadyExists, Boolean allowModifyingOtherEndOfRelationship, Boolean forceForeignKeyChanges)
   at System.Data.Objects.DataClasses.RelatedEnd.Add(IEntityWrapper wrappedEntity, Boolean applyConstraints)
   at System.Data.Objects.DataClasses.EntityReference`1.set_ReferenceValue(IEntityWrapper value)
   at System.Data.Objects.DataClasses.EntityReference`1.set_Value(TEntity value)

To correctly clone an entity framework object (at least in EF6.0) is:

/// <summary>
/// Clone a replica of this item in the database
/// </summary>
/// <returns>The cloned item</returns>
public Item CloneDeep()
{
    using (var context = new EntityObjectContext())
    {
        var item = context.Items
            .Where(i => i.ItemID == this.ItemID)
            .Single();
        context.Detach(item);
        item.EntityKey = null;
        item.ItemID = 0;
        return item;
    }
}

Solution 4

I found this looking to see if there was a better way to clone an object than I was currently using and noticed that there is a potential problem with the accepted answer if you are trying to do multiple clones...at least if you want to avoid creating your context many times...

I don't know if this is the best approach to cloning, which is why I was looking for another way. But, it works. If you need to clone an entity multiple times, you can use JSON serialization to clone...something like this (using Newtonsoft JSON).

using( var context = new Context() ) {
    Link link    = context.Links.Where(x => x.Id == someId);
    bool isFirst = true;
    foreach( var id in userIds ) {
        if( isFirst ) {
            link.UserId = id;
            isFirst     = false;
        }
        else {
            string cloneString = JsonConvert.SerializeObject(link);
            Link clone = JsonConvert.DeserializeObject<Link>(cloneString);
            clone.UserId = id;
            context.Links.Add(clone);
        }
    }
    context.SaveChanges();
}
Share:
73,802
QuantumHive
Author by

QuantumHive

Updated on July 29, 2021

Comments

  • QuantumHive
    QuantumHive almost 3 years

    In my MVVM program I have a Model class (say MyModel) from which I have an instance of reading from the database (using Entity Framework). When retrieving the object I'm presenting all the data to the user. Later on the user will be modifying some fields.
    What I want is to create the same object except for it's ID (since that ID is the primary key and auto incremented).
    So how could I approach this? I don't want to copy all fields one by one, this is not a robust approach. Because perhaps in the future the model may be modified, so this way I will have to take that into account in the cloning method.

    So is there any elegant way to copy the object and when saving in the database, it's ID gets auto incremented again? (Setting the ID to null gives me a compiler error, because it's of type int).

  • thestephenstanton
    thestephenstanton over 7 years
    Where returns a collection though. Also this won't work if your entity has an object or collection of objects. At least not for me.
  • QuantumHive
    QuantumHive over 7 years
    @THEStephenStanton The .Where() linq extension method is just an example. You could also use the .Find() method on an Entity Framework DbSet<T> for that sake. But that's not the point, the point is that EF auto-increments the ID when you add a dirty model to your DbContext.
  • Kirsten
    Kirsten about 7 years
  • kiml42
    kiml42 about 7 years
    Neat solution. Is there a straight forward way to get the new object or at least its Id?
  • marchWest
    marchWest almost 7 years
    @kiml42 - The dirty model object will update with the new ID once you call SaveChanges();
  • David Moores
    David Moores almost 7 years
    Are we sure the excerpt above doesn't also place the original record in to state modified with the new value on '.SomeOtherProperty' ? My understanding is that both of the models will have this new 'someOtherValue' once the save is run.
  • QuantumHive
    QuantumHive almost 7 years
    @DavidMoores Yes, you can be assured that the original record will not be modified. I've tested it with this gist.
  • QuantumHive
    QuantumHive almost 7 years
    This depends on your context, if you are not calling context.SaveChanges() before inserting a duplicate key, then the underlying attached entities might have duplicate keys, and that's what the exception message is rightfully complaining about. So this totally depends on your use case and implementation. You should not have duplicate entities with the same key attached to the same context. In my example, I'm obviously querying an entity from a new database context, so in that case it does works.
  • Jim B
    Jim B about 6 years
    I used this approach, but had to modify the Include section to .Include("ChildEntities") - from msdn: Dot-separated list of related objects to return in the query results.
  • Dalton
    Dalton about 6 years
    I am using this method, however, when I add the entity to the context it complains about a multiplicity constraint, it's as if the child entity does not increment it's primary key. Has anyone else had this issue and know how to solve it?
  • jaycer
    jaycer about 6 years
    @Dalton I’d suggest entering a new question, and including relevant code. I’m not sure what would be causing the issue you mention.
  • Dalton
    Dalton about 6 years
    I added a second, temporary instance of my context and used that to add the new entity and that solved my issue.
  • jaycer
    jaycer about 6 years
    @Dalton Sounds hacky
  • WtFudgE
    WtFudgE almost 6 years
    Actually this syntax is wrong, it uses "Where", which is multiple fields, should be replaced with find, first, or firstordefault
  • rubens.lopes
    rubens.lopes over 5 years
    Just a quick note. For some reason the DBA on our project has designed a enum as a decimal(2,0) and when cloning a entity like that a prop that is 1, for instance, will become 1.0 and db will throw the following exception "Parameter value ‘1.0’ is out of range" - to avoid that just cast the prop to int and reassign it
  • Rickard Liljeberg
    Rickard Liljeberg over 5 years
    I also get @Daltons issue as of recently with a 0..1 relationship.
  • riqitang
    riqitang about 5 years
    Not sure how this is the accepted answer: I try it and it still gives the "duplicate key" exceptions
  • Angel Venchev
    Angel Venchev over 3 years
    Another issue with that solution would be that if you have a circullar reference the json serialize method won't work. Example User.Group.Users
  • CervEd
    CervEd over 3 years
    @QuantumHive I am not assured that this doesn't change the original properties. I'm guessing it depends on whether or not the entities are loaded by the same context