Deep Copy Cloning of LINQ Entity Objects
Unfortunately, there’s no real slick way (built into LINQ) to clone LINQ entity objects.
My need was simple: Take a record, duplicate it, change a few select values, and then dump it back into the data source to generate a new primary key.
But I couldn’t find a good way to get a deep copy using ICloneable and sure as heck didn’t want to do it manually.
After a bit of searching, I came across a great blog posting that discussed using direct IL to make deep copies of objects. The blog post looked promising. As the author explains, “[t]he basic thing that it does is, create a DynamicMethod, get the ILGenerator, emit code in the method, compile it to a delegate, and execute the delegate.” Sounds simple enough.
NOTE: Check out the blog post (here) for the full source code; the modifications I made are mostly domain specific—the implementation works great as it is.
I added the method into the GenericController that I use to implement LINQ and the results were brillant!
// Clone the entity based on the original report.
var newReport = this.Clone(SelectOne(originalReportId));
// Remove the ID to make it generate a new primary key.
newReport.Id = 0;
// Change the necessary information.
newReport.EnteredBy = enteredBy;
newReport.Quarter = quarter;
// Insert the imported report into the system and save changes.
// Now that the imported report has a primary key, return it.
Because .Clone is a virtual method of my Controller’s base class (public virtual T Clone<T>(T entity)), it applies to any type that I happen to pass into it. In the code example above, this Import method is in the ReportsController and SelectOne returns a Report object, so Clone generates a clone based on the constructor and fields of that type. Implementation with any other LINQ controller works just the same—excellent for reuse.
Since an integer primary key, Id in the code above, cannot be null, setting it to zero (0) will force the SQL processor to generate a new primary key when the record is entered into the database—which is exactly what we want.
Very pleased with the implementation of this and how slick (and quickly) it works.