Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Entity Framework 6: Clone object except ID [closed]

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).

like image 949
QuantumHive Avatar asked Sep 08 '14 09:09

QuantumHive


4 Answers

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();
}
like image 61
QuantumHive Avatar answered Oct 29 '22 07:10

QuantumHive


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.

like image 66
jaycer Avatar answered Oct 29 '22 05:10

jaycer


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;
    }
}
like image 8
sweetfa Avatar answered Oct 29 '22 06:10

sweetfa


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();
}
like image 6
Kevin Nelson Avatar answered Oct 29 '22 07:10

Kevin Nelson