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
).
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();
}
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.
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;
}
}
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();
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With