tutorial framework example ejemplo c# asp.net-mvc entity-framework asp.net-web-api

framework - web api json c#



Cómo agregar/actualizar entidades secundarias al actualizar una entidad principal en EF (6)

Debido a que el modelo que se publica en el controlador WebApi está separado de cualquier contexto de entidad-marco (EF), la única opción es cargar el gráfico de objeto (padre, incluidos sus hijos) de la base de datos y comparar qué hijos se han agregado, eliminado o actualizado. (A menos que realice un seguimiento de los cambios con su propio mecanismo de seguimiento durante el estado desconectado (en el navegador o donde sea), que en mi opinión es más complejo que el siguiente.) Podría verse así:

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 puede tomar cualquier objeto y asigna valores de propiedad a la entidad adjunta en función del nombre de la propiedad. Si los nombres de propiedad en su modelo son diferentes de los nombres en la entidad, no puede usar este método y debe asignar los valores uno por uno.

Las dos entidades son una relación de uno a muchos (construido por el código primero con fluidez api).

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

En mi controlador WebApi, tengo acciones para crear una entidad principal (que funciona bien) y actualizar una entidad principal (que tiene algún problema). La acción de actualización se ve así:

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

Actualmente tengo dos ideas:

  1. Obtenga una entidad principal rastreada denominada existing por model.Id , y asigne valores en el model uno por uno a la entidad. Esto suena estúpido. Y en model.Children no sé qué niño es nuevo, qué niño se modifica (o incluso se elimina).

  2. Cree una nueva entidad principal a través del model , y adjúntela al DbContext y guárdela. Pero, ¿cómo puede saber DbContext el estado de los hijos (nueva adición / eliminación / modificación)?

¿Cuál es la forma correcta de implementar esta función?


Existen algunos proyectos que facilitan la interacción entre el cliente y el servidor en lo que respecta a guardar un gráfico de objeto completo.

Aquí hay dos que te gustaría ver:

Ambos proyectos anteriores reconocen las entidades desconectadas cuando se devuelve al servidor, detectan y guardan los cambios y devuelven los datos afectados por el cliente.


He estado jugando con algo como esto ...

protected void UpdateChildCollection<Tparent, Tid , Tchild>(Tparent dbItem, Tparent newItem, Func<Tparent, IEnumerable<Tchild>> selector, Func<Tchild, Tid> 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<Tid, Tchild>(); var updated = newItems?.ToDictionary(idSelector) ?? new Dictionary<Tid, Tchild>(); 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<Tchild>().Add(i.Value)); }

que puedes llamar con algo como:

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

Desafortunadamente, esto se cae si hay propiedades de colección en el tipo secundario que también necesitan actualizarse. Considerar tratar de resolver esto pasando un IRepository (con métodos básicos de CRUD) que sería responsable de llamar a UpdateChildCollection por sí solo. Llamaría al repositorio en lugar de llamadas directas a DbContext.Entry.

No tengo idea de cómo funcionará todo a escala, pero no estoy seguro de qué más hacer con este problema.


Ok muchachos. Tuve esta respuesta una vez, pero la perdí en el camino. tortura absoluta cuando sabes que hay una mejor manera, ¡pero no puedo recordarla ni encontrarla! Es muy sencillo. Acabo de probarlo de varias maneras.

var parent = _dbContext.Parents .Where(p => p.Id == model.Id) .Include(p => p.Children) .FirstOrDefault(); parent.Children = _dbContext.Children.Where(c => <Query for New List Here>); _dbContext.Entry(parent).State = EntityState.Modified; _dbContext.SaveChanges();

¡Puede reemplazar toda la lista por una nueva! El código SQL eliminará y agregará entidades según sea necesario. No hay necesidad de preocuparse por eso. Asegúrese de incluir la colección infantil o ningún dado. ¡Buena suerte!


Si está utilizando EntityFrameworkCore, puede hacer lo siguiente en la acción posterior de su controlador (El método Adjuntar adjunta recursivamente propiedades de navegación, incluidas colecciones):

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

Se supone que cada entidad que se actualizó tiene todas las propiedades establecidas y proporcionadas en los datos de publicación del cliente (por ejemplo, no funcionará para la actualización parcial de una entidad).

También debe asegurarse de estar utilizando un contexto de base de datos de marco de entidad nuevo / dedicado para esta operación.


Solo prueba de concepto Controler.UpdateModel no funcionará correctamente.

Clase completa here :

const string PK = "Id"; protected Models.Entities con; protected System.Data.Entity.DbSet<T> 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; } }