Come aggiungere / aggiornare quadro figlio quando si aggiorna un’entity framework padre in EF

Le due entity framework sono una relazione uno-a-molti (costruita dal codice prima api fluente).

public class Parent { public Parent() { this.Children = new List(); } public int Id { get; set; } public virtual ICollection Children { get; set; } } public class Child { public int Id { get; set; } public int ParentId { get; set; } public string Data { get; set; } } 

Nel mio controller WebApi ho delle azioni per creare un’entity framework padre (che funziona bene) e aggiornare un’ quadro padre (che ha qualche problema). L’azione di aggiornamento ha il seguente aspetto:

 public void Update(UpdateParentModel model) { //what should be done here? } 

Attualmente ho due idee:

  1. Ottieni un’ quadro padre monitorata denominata existing per model.Id e assegna valori al model uno per uno all’ quadro. Sembra stupido. E nel model.Children I bambini non so quale bambino è nuovo, quale bambino viene modificato (o addirittura cancellato).

  2. Creare una nuova quadro genitore tramite model e collegarla a DbContext e salvarla. Ma come può DbContext conoscere lo stato dei bambini (nuovo add / delete / modified)?

Qual è il modo corretto di implementare questa funzione?

Poiché il modello che viene pubblicato sul controller WebApi viene rimosso da qualsiasi contesto entity-framework (EF), l’unica opzione è caricare il grafico dell’object (genitore compresi i suoi figli) dal database e confrontare quali bambini sono stati aggiunti, eliminati o aggiornato. (A meno che tu non insegui le modifiche con il tuo meccanismo di tracciamento durante lo stato di distacco (nel browser o ovunque) che a mio parere è più complesso del seguente.) Potrebbe assomigliare a questo:

 public void Update(UpdateParentModel model) { var existingParent = _dbContext.Parents .Where(p => p.Id == model.Id) .Include(p => p.Children) .SingleOrDefault(); if (existingParent != null) { // Update parent _dbContext.Entry(existingParent).CurrentValues.SetValues(model); // Delete children foreach (var existingChild in existingParent.Children.ToList()) { if (!model.Children.Any(c => c.Id == existingChild.Id)) _dbContext.Children.Remove(existingChild); } // Update and Insert children foreach (var childModel in model.Children) { var existingChild = existingParent.Children .Where(c => c.Id == childModel.Id) .SingleOrDefault(); if (existingChild != null) // Update child _dbContext.Entry(existingChild).CurrentValues.SetValues(childModel); else { // Insert child var newChild = new Child { Data = childModel.Data, //... }; existingParent.Children.Add(newChild); } } _dbContext.SaveChanges(); } } 

...CurrentValues.SetValues può prendere qualsiasi object e mappa i valori delle proprietà sull’ quadro associata in base al nome della proprietà. Se i nomi delle proprietà nel modello sono diversi dai nomi nell’ quadro, non è ansible utilizzare questo metodo e assegnare i valori uno per uno.

Ho fatto casino con qualcosa di simile …

 protected void UpdateChildCollection(Tparent dbItem, Tparent newItem, Func> selector, Func idSelector) where Tchild : class { var dbItems = selector(dbItem).ToList(); var newItems = selector(newItem).ToList(); if (dbItems == null && newItems == null) return; var original = dbItems?.ToDictionary(idSelector) ?? new Dictionary(); var updated = newItems?.ToDictionary(idSelector) ?? new Dictionary(); var toRemove = original.Where(i => !updated.ContainsKey(i.Key)).ToArray(); var removed = toRemove.Select(i => DbContext.Entry(i.Value).State = EntityState.Deleted).ToArray(); var toUpdate = original.Where(i => updated.ContainsKey(i.Key)).ToList(); toUpdate.ForEach(i => DbContext.Entry(i.Value).CurrentValues.SetValues(updated[i.Key])); var toAdd = updated.Where(i => !original.ContainsKey(i.Key)).ToList(); toAdd.ForEach(i => DbContext.Set().Add(i.Value)); } 

che puoi chiamare con qualcosa come:

 UpdateChildCollection(dbCopy, detached, p => p.MyCollectionProp, collectionItem => collectionItem.Id) 

Sfortunatamente, questo tipo cade se ci sono proprietà di raccolta sul tipo di figlio che devono essere aggiornate. Considerando il tentativo di risolvere questo problema passando un IRepository (con i metodi CRUD di base) che sarebbe responsabile di chiamare UpdateChildCollection da solo. Chiamerei il repository invece delle chiamate dirette a DbContext.Entry.

Non ho idea di come tutto questo si realizzerà su larga scala, ma non sapremo cosa fare di questo problema.

Se si utilizza EntityFrameworkCore, è ansible eseguire quanto segue nel controller dopo l’azione (il metodo Attach associa in modo ricorsivo proprietà di navigazione, incluse le raccolte):

 _context.Attach(modelPostedToController); IEnumerable unchangedEntities = _context.ChangeTracker.Entries().Where(x => x.State == EntityState.Unchanged); foreach(EntityEntry ee in unchangedEntities){ ee.State = EntityState.Modified; } await _context.SaveChangesAsync(); 

Si presume che ogni quadro che è stata aggiornata abbia tutte le proprietà impostate e fornite nei dati post dal client (ad esempio non funzionerà per l’aggiornamento parziale di un’ quadro).

È inoltre necessario assicurarsi che si stia utilizzando un contesto del database framework quadro nuovo / dedicato per questa operazione.

Solo una prova del concetto Controler.UpdateModel non funzionerà correttamente.

Classe completa qui :

 const string PK = "Id"; protected Models.Entities con; protected System.Data.Entity.DbSet model; private void TestUpdate(object item) { var props = item.GetType().GetProperties(); foreach (var prop in props) { object value = prop.GetValue(item); if (prop.PropertyType.IsInterface && value != null) { foreach (var iItem in (System.Collections.IEnumerable)value) { TestUpdate(iItem); } } } int id = (int)item.GetType().GetProperty(PK).GetValue(item); if (id == 0) { con.Entry(item).State = System.Data.Entity.EntityState.Added; } else { con.Entry(item).State = System.Data.Entity.EntityState.Modified; } } 

Ci sono alcuni progetti là fuori che rendono l’interazione tra il client e il server più semplice per quanto riguarda il salvataggio di un intero object grafico.

Qui ci sono due che vorresti vedere:

  • Entità tracciabili
  • Brezza#

Entrambi i progetti sopra riportati riconoscono le quadro disconnesse quando vengono restituite al server, rilevano e salvano le modifiche e ritornano ai dati interessati del client.

Prova ad usare il ciclo ForEach supportato da LINQ in questo caso:

 public void Update(ParentModel model, string newData) { var parent = _dbContext.Parents .Where(p => p.Id == model.Id) .Include(p => p.Children) .FirstOrDefault(); if(parent != null){ //update parent ... //update children if(parent.Children != null && parent.Children.Any()){ parent.Children.ForEach(x => x.Data = newData); } _dbContext.SaveChanges(); }