Actualizar relaciones al guardar cambios de objetos EF4 POCO

Resuelto peterfoldi asked hace 14 años • 5 respuestas

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 POCO BlogPost generada por 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 lo envío a otra capa (Ver en la aplicación MVC). Más tarde recupero el BlogPost actualizado con propiedades modificadas y relaciones modificadas. Por ejemplo, tenía etiquetas "A", "B" y "C", y las nuevas etiquetas son "C" y "D". En mi ejemplo particular, no hay etiquetas nuevas y las propiedades de las etiquetas nunca cambian, por lo que lo único que se debe guardar son las relaciones modificadas. Ahora necesito guardar esto en otro ObjectContext. (Actualización: ahora intenté hacerlo en la misma instancia de contexto y también fallé).

El problema: no puedo hacer que guarde las relaciones correctamente. Probé todo lo que encontré:

  • Controller.UpdateModel y Controller.TryUpdateModel no funcionan.
  • Obtener el BlogPost antiguo del contexto y luego modificar la colección no funciona. (con diferentes métodos del siguiente punto)
  • Esto probablemente funcionaría, pero espero que sea sólo una solución alternativa, no la solución :(.
  • Probé las funciones Adjuntar/Agregar/CambiarObjectState para BlogPost y/o Etiquetas en todas las combinaciones posibles. Fallido.
  • Esto parece lo que necesito, pero no funciona (intenté solucionarlo, pero no pude debido a mi problema).
  • Probé ChangeState/Add/Attach/... los objetos de relación del contexto. Fallido.

"No funciona" significa en la mayoría de los casos que trabajé en la "solución" dada hasta que no produce errores y guarda al menos las propiedades de BlogPost. Lo que sucede con las relaciones varía: generalmente las etiquetas se agregan nuevamente a la tabla de etiquetas con nuevas PK y la publicación de blog guardada hace referencia a esas y no a las originales. Por supuesto, las etiquetas devueltas tienen PK, y antes de los métodos de guardar/actualizar verifico las PK y son iguales a las de la base de datos, por lo que probablemente EF piensa que son objetos nuevos y esas PK son las temporales.

Un problema que conozco y que podría hacer imposible encontrar una solución simple automatizada: cuando se cambia la colección de un objeto POCO, eso debería suceder mediante 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 BlogPost actualizado, eso no sucedió. Esto significa que tal vez no haya una solución simple para mi problema, pero eso me entristecería mucho y odiaría el triunfo de EF4-POCO-MVC :(. También eso significaría que EF no puede hacer esto en el entorno MVC, sea cual sea. Se utilizan tipos de objetos EF4 :(. Creo que el seguimiento de cambios basado en instantáneas debería descubrir que el BlogPost modificado tiene relaciones con etiquetas con PK existentes.

Por cierto: creo que ocurre el mismo problema con las relaciones de uno a muchos (Google y mi colega lo dicen). Lo intentaré en casa, pero incluso si funciona, no me ayuda en mis seis relaciones de muchos a muchos en mi aplicación :(.

peterfoldi avatar Sep 03 '10 17:09 peterfoldi
Aceptado

Intentémoslo de esta manera:

  • Adjunte BlogPost al contexto. Después de adjuntar el objeto al contexto, el estado del objeto, todos los objetos relacionados y todas las relaciones se establece en Sin cambios.
  • Utilice context.ObjectStateManager.ChangeObjectState para configurar su BlogPost como Modificado
  • Iterar a través de la colección de etiquetas
  • Utilice 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 falsas esperanzas de que EF hiciera 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 encontraste mi pregunta en MSDN . En realidad, hay muchas preguntas de este tipo en Internet. El problema es que no está claro cómo afrontar este escenario. Así que echemos un vistazo al problema:

Antecedentes del problema

EF necesita realizar 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 realizar un seguimiento de los cambios. ObjectContext puede rastrear cambios solo para entidades adjuntas. Las entidades que se crean fuera del ObjectContext no reciben ningún seguimiento.

Descripción del problema

Según la descripción anterior, podemos afirmar claramente que EF es más adecuado para escenarios conectados donde la entidad siempre está adjunta al contexto, típico de 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 a un nuevo contexto y persistir. La recreación generalmente ocurre fuera del alcance del contexto (arquitectura en capas con ignorancia persistente).

Solución

Entonces, ¿cómo afrontar un escenario tan desconectado? Al usar clases de POCO tenemos 3 formas de lidiar con el seguimiento de cambios:

  • Instantánea: requiere el mismo contexto = inútil para un escenario desconectado
  • Proxies de seguimiento dinámico: requiere el mismo contexto = inútil para escenarios desconectados
  • Sincronización manual.

La sincronización manual en una sola entidad es una tarea sencilla. Solo necesita adjuntar una entidad y llamar a AddObject para insertar, DeleteObject para eliminar o establecer el estado en ObjectStateManager en Modificado para actualizar. El verdadero problema surge cuando tienes que lidiar con un gráfico de objetos en lugar de una entidad única. Este dolor es aún peor cuando tienes que lidiar con asociaciones independientes (aquellas que no usan la propiedad de clave externa) y relaciones de muchos a muchos. 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 sincronización manual se propone como solución en la documentación de MSDN: 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, utilice uno de los siguientes métodos.

Los métodos mencionados son ChangeObjectState y ChangeRelationshipState de ObjectStateManager = seguimiento de cambios manual. Una propuesta similar se encuentra en otro artículo de documentación de MSDN: Definición y gestión de relaciones dice:

Si está trabajando con objetos desconectados, debe gestionar manualmente la sincronización.

Además, hay una publicación de blog relacionada con EF v1 que critica exactamente este comportamiento de EF.

Razón de la solución

EF tiene muchas operaciones y configuraciones "útiles" como Refresh , Load , ApplyCurrentValues , ApplyOriginalValues , MergeOption , etc. Pero según mi investigación, todas estas características funcionan solo para una sola entidad y afectan solo las propiedades escalares (= no las propiedades y relaciones de navegación). Prefiero no probar estos métodos con tipos complejos anidados en una entidad.

Otra solución propuesta

En lugar de una funcionalidad de combinación real, el equipo de EF proporciona algo llamado Entidades de seguimiento automático (STE) que no resuelven el problema. En primer lugar, STE funciona sólo si se utiliza la misma instancia para todo el procesamiento. En la aplicación web, no es así a menos que almacene la instancia en estado de vista o sesión. Debido a eso, no estoy muy contento con el uso de EF y voy a verificar las características de NHibernate. La primera observación dice que NHibernate quizás tenga dicha funcionalidad .

Conclusión

Terminaré con estas suposiciones con un enlace único a otra pregunta relacionada en el foro de MSDN. Consulte la respuesta de Zeeshan Hirani. Es autor de Entity Framework 4.0 Recipes . Si dice que no se admite la combinación automática de gráficos de objetos, le creo.

Pero aún existe la posibilidad de que esté completamente equivocado y exista alguna funcionalidad de combinación automática en EF.

Edición 2:

Como puede ver, esto ya se agregó a MS Connect como sugerencia en 2007. MS lo cerró como algo que se debe hacer en la próxima versión, pero en realidad no se ha hecho nada para mejorar esta brecha, excepto STE.

Ladislav Mrnka avatar Sep 03 '2010 11:09 Ladislav Mrnka

Tengo una solución al problema descrito anteriormente por Ladislav. He creado un método de extensión para DbContext que realizará automáticamente agregar/actualizar/eliminar en función de una diferencia del gráfico proporcionado y el gráfico persistente.

Actualmente, al utilizar Entity Framework, deberá realizar las actualizaciones de los contactos manualmente, verificar si cada contacto es nuevo y agregarlo, verificar si está actualizado y editarlo, verificar si se eliminó y luego eliminarlo de la base de datos. Una vez que tienes que hacer esto para algunos agregados diferentes en un sistema grande, comienzas a darte cuenta de que debe haber una manera mejor y más genérica.

Eche un vistazo y vea si puede ayudar http://refactorthis.wordpress.com/2012/12/11/introtaining-graphdiff-for-entity-framework-code-first-allowing-automated-updates-of-a- grafico-de-entidades-separadas/

Puede ir directamente al código aquí https://github.com/refactorthis/GraphDiff

refactorthis avatar Dec 11 '2012 12:12 refactorthis

Sé que es tarde para el OP, pero como este es un problema muy común, publiqué esto en caso de que le sirva a alguien más. He estado jugando con este problema y creo que tengo una solución bastante simple, lo que hago es:

  1. Guarde el objeto principal (Blogs, por ejemplo) estableciendo su estado en Modificado.
  2. Consulta la base de datos para encontrar el objeto actualizado, incluidas las colecciones que necesito actualizar.
  3. Consulta y convierte .ToList() las entidades que quiero que incluya mi colección.
  4. Actualice las colecciones del objeto principal a la Lista que obtuve en el paso 3.
  5. 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 múltiples relaciones.

c0y0teX avatar Jun 06 '2012 01:06 c0y0teX

El equipo de Entity Framework es consciente de que se trata de un problema de usabilidad y planea solucionarlo 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 trabajar más después de EF6. He creado este elemento de trabajo para realizar un seguimiento del problema: http://entityframework.codeplex.com/workitem/864 El elemento de trabajo también contiene un enlace al elemento de voz del usuario para esto. Le recomiendo que lo vote si lo tiene. aún no lo he hecho.

Si esto le afecta, vote por la función en

http://entityframework.codeplex.com/workitem/864

Eric J. avatar Feb 11 '2013 20:02 Eric J.