Entity Framework 6: Clone object except ID
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();
}
QuantumHive
Updated on July 29, 2021Comments
-
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'sID
(since thatID
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 typeint
). -
thestephenstanton over 7 yearsWhere 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 over 7 years@THEStephenStanton The
.Where()
linq extension method is just an example. You could also use the.Find()
method on an Entity FrameworkDbSet<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 about 7 years
-
kiml42 about 7 yearsNeat solution. Is there a straight forward way to get the new object or at least its Id?
-
marchWest almost 7 years@kiml42 - The dirty model object will update with the new ID once you call SaveChanges();
-
David Moores almost 7 yearsAre 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 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 almost 7 yearsThis 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 about 6 yearsI 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 about 6 yearsI 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 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 about 6 yearsI added a second, temporary instance of my context and used that to add the new entity and that solved my issue.
-
jaycer about 6 years@Dalton Sounds hacky
-
WtFudgE almost 6 yearsActually this syntax is wrong, it uses "Where", which is multiple fields, should be replaced with find, first, or firstordefault
-
rubens.lopes over 5 yearsJust 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 over 5 yearsI also get @Daltons issue as of recently with a 0..1 relationship.
-
riqitang about 5 yearsNot sure how this is the accepted answer: I try it and it still gives the "duplicate key" exceptions
-
Angel Venchev over 3 yearsAnother 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 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