Sto provando a clonare in profondità / copiare un oggetto entità che contiene elementi figlio dello stesso tipo. L'articolo ha anche parametri, che dovrebbero essere clonati. Il ItemType, tuttavia, deve essere lasciato come riferimento al ItemType esistente.
Con l'aiuto di Stackoverflow ( copia / clone di entità Entity Framework 5 ) ho trovato il seguente tentativo piuttosto pessimo:
public Item DeepCloneItem(Item item)
{
Item itemClone = db.Items //Level 1
.Include(i => i.ChildrenItems.Select(c => c.ChildrenItems )) //3 Levels
.Include(i => i.Parameters) //Level 1 Params
.Include(i => i.ChildrenItems.Select(c => c.Parameters)) //Level 2 Params
.Include(i => i.ChildrenItems.Select(c => c.ChildrenItems
.Select(cc => cc.Parameters))) //Level 3 Params
.AsNoTracking()
.FirstOrDefault(i => i.ItemID == item.ItemID);
db.Items.Add(itemClone);
db.SaveChanges();
return itemClone;
}
Per un livello di profondità fisso di 3 questo tentativo funziona come un fascino. Tuttavia, come puoi vedere, questo non sta diventando molto carino ad ogni livello più profondo. Il design consente un numero infinito di nidificazione (nel mio contesto, tuttavia, non dovrebbero esserci più di 5 livelli).
C'è qualche possibilità di aggiungere dinamicamente Include a IQueryable a seconda della profondità massima?
Questa è l'entità dell'oggetto da clonare:
public class Item
{
public int ItemID { get; set; }
public int? ParentItemID { get; set; }
[ForeignKey("ParentItemID")]
public virtual Item ParentItem { get; set; }
public virtual ICollection<Item> ChildrenItems { get; set; }
[InverseProperty("Item")]
public virtual ICollection<Parameter> Parameters { get; set; }
public ItemTypeIds ItemTypeID { get; set; }
[ForeignKey("ItemTypeID")]
public virtual ItemType ItemType { get; set; }
}
Ho trovato un approccio più generico per questo problema. Per chiunque possa incontrare un problema simile, ecco come ho risolto per ora:
public Item DeepCloneItem(Item item)
{
Item itemClone = db.Items.FirstOrDefault(i => i.ItemID == item.ItemID);
deepClone(itemClone);
db.SaveChanges();
return itemClone;
}
private void deepClone(Item itemClone)
{
foreach (Item child in itemClone.ChildrenItems)
{
deepClone(child);
}
foreach(Parameter param in itemClone.Parameters)
{
db.Entry(param).State = EntityState.Added;
}
db.Entry(itemClone).State = EntityState.Added;
}
Tenere presente che la chiamata ricorsiva deve essere prima dell'assegnazione EntityState.Added. Altrimenti, la ricorsione si fermerà al secondo livello. Inoltre, il metodo ricorsivo deve essere chiamato con un'entità in uno stato Attached. Altrimenti, la ricorsione si fermerà anche al secondo livello.
Considera di sostituire la ricorsione con un approccio iterativo se il tuo albero delle entità è molto profondo. Per maggiori informazioni, date un'occhiata a: Wikipedia Ricorsione contro iterazione
Feedback e miglioramenti benvenuti!