asp.net mvc - sqlquery - Actualizar relaciones al guardar cambios de objetos POCO EF4
entity framework tutorial (5)
El equipo de Entity Framework es consciente de que se trata de un problema de usabilidad y planea abordarlo después de EF6.
Del equipo de Entity Framework:
Este es un problema de usabilidad del que somos conscientes y es algo en lo que hemos estado pensando y planeamos hacer más trabajo en post-EF6. Creé este elemento de trabajo para rastrear el problema: entityframework.codeplex.com/workitem/864 El elemento de trabajo también contiene un enlace al elemento de voz del usuario para esto. Lo invito a votar si lo tiene no hecho así ya
Si esto te afecta, vota por la función en
Entity Framework 4, objetos POCO y ASP.Net MVC2. Tengo una relación de muchos a muchos, digamos entre las entidades BlogPost y Tag. Esto significa que en mi clase T4 BlogPost generada T4 tengo:
public virtual ICollection<Tag> Tags {
// getter and setter with the magic FixupCollection
}
private ICollection<Tag> _tags;
Solicito un BlogPost y las etiquetas relacionadas de una instancia de ObjectContext y las envío a otra capa (Ver en la aplicación MVC). Más tarde recibo el BlogPost actualizado con las propiedades modificadas y las relaciones cambiadas. Por ejemplo, tenía las etiquetas "A" "B" y "C", y las nuevas etiquetas son "C" y "D". En mi ejemplo particular, no hay nuevas etiquetas y las propiedades de las etiquetas nunca cambian, por lo que lo único que debe guardarse son las relaciones cambiadas. Ahora necesito guardar esto en otro ObjectContext. (Actualización: ahora traté de hacerlo en la misma instancia de contexto y también fallé).
El problema: no puedo hacer que guarde las relaciones correctamente. Intenté todo lo que encontré:
- Controller.UpdateModel y Controller.TryUpdateModel no funcionan.
- Obtener el antiguo BlogPost del contexto y luego modificar la colección no funciona. (con diferentes métodos del próximo punto)
- This probablemente funcionaría, pero espero que esto sea solo una solución, no la solución :(.
- Se probaron las funciones Adjuntar / Agregar / CambiarObjetoEstado para BlogPost y / o Etiquetas en todas las combinaciones posibles. Ha fallado.
- This parece a lo que necesito, pero no funciona (traté de solucionarlo, pero no puedo por mi problema).
- Intentó Cambiar Estado / Agregar / Adjuntar / ... los objetos de relación del contexto. Ha fallado.
"No funciona" significa en la mayoría de los casos que trabajé en la "solución" dada hasta que no produce errores y se guardan al menos las propiedades de BlogPost. Lo que sucede con las relaciones varía: por lo general, las etiquetas se vuelven a agregar a la tabla de etiquetas con nuevos PK y el BlogPost guardado hace referencia a esos y no a los originales. Por supuesto, las etiquetas devueltas tienen PK, y antes de los métodos de guardar / actualizar compruebo las PK y son iguales a las de la base de datos, por lo que probablemente EF piense que son objetos nuevos y esas PK son temporales.
Un problema que conozco y que podría hacer que sea imposible encontrar una solución simple automatizada: cuando se cambia la colección de un objeto POCO, eso debería ocurrir por la propiedad de colección virtual mencionada anteriormente, porque entonces el truco FixupCollection actualizará las referencias inversas en el otro extremo de la relación de muchos a muchos. Sin embargo, cuando una vista "devuelve" un objeto actualizado de BlogPost, eso no sucedió. Esto significa que quizás no haya una solución simple a mi problema, pero eso me pondría muy triste y odiaría el triunfo de EF4-POCO-MVC :(. Eso también significaría que EF no puede hacer esto en el entorno MVC, lo que sea Se utilizan los tipos de objeto EF4 :(. Creo que el seguimiento de cambios basado en instantáneas debería descubrir que el BlogPost modificado tiene relaciones con las etiquetas con PK existentes.
Por cierto: creo que el mismo problema ocurre con las relaciones uno a muchos (google y mi colega lo dicen). Lo probaré en casa, pero incluso si eso funciona, eso no me ayuda en mis seis relaciones de muchos a muchos en mi aplicación :(.
Probémoslo de esta manera:
- Adjunte BlogPost al contexto. Después de adjuntar un objeto al contexto del estado del objeto, todos los objetos relacionados y todas las relaciones se configuran como Sin cambios.
- Use context.ObjectStateManager.ChangeObjectState para configurar su BlogPost a Modified
- Itera a través de la colección Tag
- Use context.ObjectStateManager.ChangeRelationshipState para establecer el estado de la relación entre la etiqueta actual y BlogPost.
- Guardar cambios
Editar:
Supongo que uno de mis comentarios te dio una falsa esperanza de que EF haga la fusión por ti. Jugué mucho con este problema y mi conclusión dice que EF no hará esto por ti. Creo que también ha encontrado mi pregunta en MSDN . En realidad, hay muchas de esas preguntas en Internet. El problema es que no está claramente establecido cómo lidiar con este escenario. Así que echemos un vistazo al problema:
Antecedentes del problema
EF necesita hacer un seguimiento de los cambios en las entidades para que la persistencia sepa qué registros deben actualizarse, insertarse o eliminarse. El problema es que es responsabilidad de ObjectContext hacer un seguimiento de los cambios. ObjectContext puede rastrear cambios solo para entidades adjuntas. Las entidades que se crean fuera de ObjectContext no se rastrean en absoluto.
Descripción del problema
En base a la descripción anterior, podemos afirmar con claridad que EF es más adecuado para escenarios conectados donde la entidad siempre se adjunta al contexto, típica para la aplicación WinForm. Las aplicaciones web requieren un escenario desconectado donde el contexto se cierra después del procesamiento de la solicitud y el contenido de la entidad se pasa como respuesta HTTP al cliente. La siguiente solicitud HTTP proporciona contenido modificado de la entidad que debe recrearse, adjuntarse al nuevo contexto y persistir. La recreación generalmente ocurre fuera del alcance del contexto (arquitectura estratificada con ignorancia de persistencia).
Solución
Entonces, ¿cómo lidiar con este escenario desconectado? Al usar las clases de POCO, tenemos 3 formas de lidiar con el seguimiento de cambios:
- Instantánea: requiere el mismo contexto = inútil para el escenario desconectado
- Proxies de seguimiento dinámico: requiere el mismo contexto = inútil para el escenario desconectado
- Sincronización manual.
La sincronización manual en una sola entidad es tarea fácil. Solo tiene que adjuntar entidad y llamar a AddObject para insertar, DeleteObject para eliminar o establecer estado en ObjectStateManager a Modificado para actualizar. El verdadero dolor viene cuando tienes que lidiar con un gráfico de objetos en lugar de una sola entidad. Este dolor es aún peor cuando tiene que tratar con asociaciones independientes (aquellas que no usan propiedad de Clave extranjera) y muchas o muchas relaciones. En ese caso, debe sincronizar manualmente cada entidad en el gráfico de objetos, pero también cada relación en el gráfico de objetos.
La documentación de MSDN propone la sincronización manual como solución: adjuntar y separar objetos dice:
Los objetos se adjuntan al contexto del objeto en un estado Sin cambios. Si necesita cambiar el estado de un objeto o la relación porque sabe que su objeto se modificó en estado separado, use uno de los siguientes métodos.
Los métodos mencionados son ChangeObjectState y ChangeRelationshipState of ObjectStateManager = seguimiento de cambio manual. Una propuesta similar se encuentra en otro artículo de documentación de MSDN: Definición y administración de relaciones dice:
Si está trabajando con objetos desconectados, debe administrar la sincronización manualmente.
Además, hay una publicación de blog relacionada con EF v1 que critica exactamente este comportamiento de EF.
Motivo de la solución
EF tiene muchas operaciones y configuraciones "útiles" como Refresh , Load , ApplyCurrentValues ApplyOriginalValues ApplyCurrentValues , ApplyOriginalValues ApplyCurrentValues , ApplyOriginalValues , etc. Pero según mi investigación, todas estas características funcionan solo para entidades individuales y solo afectan a las propiedades escalares (= propiedades y relaciones de navegación no). Prefiero no probar estos métodos con tipos complejos anidados en entidad.
Otra solución propuesta
En lugar de la funcionalidad real de Merge, el equipo de EF proporciona algo llamado entidades de seguimiento automático (STE) que no resuelven el problema. En primer lugar, STE funciona solo si la misma instancia se usa para todo el proceso. En la aplicación web no es el caso a menos que almacene la instancia en estado de vista o sesión. Debido a eso, estoy muy descontento con el uso de EF y voy a verificar las características de NHibernate. La primera observación dice que NHibernate quizás tenga tal functionality .
Conclusión
Terminaré estas suposiciones con un solo enlace a otra pregunta relacionada en el foro de MSDN. Verifique la respuesta de Zeeshan Hirani. Es autor de Entity Framework 4.0 Recipes . Si dice que la combinación automática de gráficos de objetos no es compatible, le creo.
Pero aún existe la posibilidad de que esté completamente equivocado y de que exista alguna funcionalidad de fusión automática en EF.
Editar 2:
Como puede ver, esto ya se agregó a MS Connect como sugerencia en 2007. MS lo ha cerrado como algo por hacer en la próxima versión, pero en realidad no se ha hecho nada para mejorar esta brecha, excepto STE.
Sé que es tarde para el OP, pero como este es un problema muy común, publiqué esto en caso de que sirva a otra persona. He estado jugando con este tema y creo que obtuve una solución bastante simple, lo que hago es:
- Guarde el objeto principal (Blogs, por ejemplo) estableciendo su estado en Modificado.
- Consulte la base de datos para el objeto actualizado, incluidas las colecciones que necesito actualizar.
- Consulta y convierte .ToList () las entidades que quiero que incluya mi colección.
- Actualice la (s) colección (es) del objeto principal a la Lista que obtuve del paso 3.
- Guardar cambios();
En el siguiente ejemplo "dataobj" y "_categories" son los parámetros recibidos por mi controlador "dataobj" es mi objeto principal, y "_categories" es un IEnumerable que contiene los ID de las categorías que el usuario seleccionó en la vista.
db.Entry(dataobj).State = EntityState.Modified;
db.SaveChanges();
dataobj = db.ServiceTypes.Include(x => x.Categories).Single(x => x.Id == dataobj.Id);
var it = _categories != null ? db.Categories.Where(x => _categories.Contains(x.Id)).ToList() : null;
dataobj.Categories = it;
db.SaveChanges();
Incluso funciona para relaciones múltiples
Tengo una solución al problema que fue descrito arriba por Ladislav. He creado un método de extensión para el DbContext que automáticamente realizará el add / update / delete basado en un diff del gráfico proporcionado y el gráfico persistente.
Actualmente, al usar Entity Framework, deberá realizar las actualizaciones de los contactos manualmente, verificar si cada contacto es nuevo y agregar, verificar si está actualizado y editar, verificar si se eliminó y luego eliminarlo de la base de datos. Una vez que tenga que hacer esto para unos pocos agregados diferentes en un sistema grande, comenzará a darse cuenta de que debe haber una forma mejor y más genérica.
Por favor, eche un vistazo y vea si puede ayudar http://refactorthis.wordpress.com/2012/12/11/introducing-graphdiff-for-entity-framework-code-first-allowing-automated-updates-of-a-graph-of-detached-entities/
Puede ir directamente al código aquí https://github.com/refactorthis/GraphDiff
Todas las respuestas fueron excelentes para explicar el problema, pero ninguna de ellas realmente resolvió el problema para mí.
Descubrí que si no usaba la relación en la entidad padre, sino que simplemente agregaba y eliminaba las entidades secundarias, todo funcionaba bien.
Perdón por el VB, pero en eso está escrito el proyecto en el que estoy trabajando.
El "Informe" de la entidad matriz tiene una relación uno a muchos con "ReportRole" y tiene la propiedad "ReportRoles". Los nuevos roles son pasados por una cadena separada por comas de una llamada Ajax.
La primera línea eliminará todas las entidades secundarias, y si usé "report.ReportRoles.Remove (f)" en lugar de "db.ReportRoles.Remove (f)" obtendría el error.
report.ReportRoles.ToList.ForEach(Function(f) db.ReportRoles.Remove(f))
Dim newRoles = If(String.IsNullOrEmpty(model.RolesString), New String() {}, model.RolesString.Split(","))
newRoles.ToList.ForEach(Function(f) db.ReportRoles.Add(New ReportRole With {.ReportId = report.Id, .AspNetRoleId = f}))