Example structure
public class Page
{
public int PageId { get; set; }
public string Prop1 { get; set; }
public string Prop2 { get; set; }
public virtual List<Section> Sections { get; set; }
}
public class Section
{
public int SectionId { get; set; }
public int PageId { get; set; }
public virtual Page Page { get; set; }
public virtual List<Heading> Headings { get; set; }
}
public class Heading
{
public int HeadingId { get; set; }
public int SectionId { get; set; }
public virtual Section Section { get; set; }
}
It's worth noting that my actual structure has more levels than this but this should be enough to explain what I'm trying to achieve.
So I load my Page
object I then Clone that object and make some minor changes to the properties of Page
i.e. Prop1
, Prop2
Page pageFromDb = getPageMethod();
Page clonedPage = pageFromDb.Clone();
clonedPage.Prop1 = clonedPage.Prop1 + " Cloned";
addPageMethod(clonedPage); //Adds the page to db
In the example above clonedPage
structure is fine and a new Page
is added to the database. However I believe because the Id's of the child objects are set and the relationship of the children is always one to many. The original object pageFromDb
will lose all it children as entity framework instead of creating new Section
objects for the cloned Page
will update the Section.PageId
to the newly inserted page.
I believe a fix for this would be to foreach
, foreach
, etc. and set all the Id's to 0
before inserting then entity framework will create new records foreach object. Is there any easier/better way to clone an object in an entity framework environment.?
In order for Entity Framework to treat the clone as an entire new object graph when persisting the graph, all entities in the graph need to be disconnected from the context in which the root entity was retrieved.
This can be done using the AsNoTracking
method on the context.
For example, this will pull a page and associated sections graph from the database and turn off tracking. Effectively this is a clone as if you add this to your Page DbSet and save it will create an entirely new object graph in the database. I.e. a new Page entity and new Section entities accordingly. Note, you wont need to call your Clone
method.
var clone = context.Pages .AsNoTracking() .Including(pages => pages.Sections) .Single(...); context.Pages.Add(clone); context.SaveChanges(); // creates an entirely new object graph in the database
Try this!
public Page CopyPage(int pageID) { using(Context context = new Context()) { context.Configuration.LazyLoadingEnabled = false; Page dbPage = context.Pages.Where(p => p.PageId == pageID).Include(s => s.Sections.Select(s => s.Section)).First(); Page page = dbPage.Clone(); page.PageId = 0; for (int i = 0; i < dbPage .Sections.Count; i++) page.Sections[i] = new Section { SectionId = 0, PageId = 0, Page = null, Headings = dbPage[i].Headings }; return page; } } public Page Clone() { Object page = this.GetType().InvokeMember("", BindingFlags.CreateInstance, null, this, null); foreach(PropertyInfo propertyInfo in this.GetType().GetProperties()) { if(propertyInfo.CanWrite) { propertyInfo.SetValue(page, propertyInfo.GetValue(this, null), null); } } return page; }
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