¿Cómo se combinan con gracia gráficos de objetos después de NHibernate StaleObjectStateException?
fluent-nhibernate optimistic-locking (1)
Estamos intentando combinar objetos después de lanzar una StaleObjectStateException para guardar una copia combinada.
Aquí está nuestra situación ambiental:
- Artículo de lista
- Sistema multiusuario
- Aplicación de escritorio WPF, base de datos de SQL Server 2008
- NHibernate 3.1.0.4000, FluentNHibernate 1.2.0.712
- Sesiones globales de NHibernate de larga duración [por el momento. Entendemos que la sesión por presentador es el patrón recomendado, pero no tenemos tiempo para convertir en el cronograma de nuestro proyecto en este momento.]
- De arriba hacia abajo guarda y navegación de la propiedad (es decir, guardamos el objeto de nivel superior (en este documento llamado Parent) en nuestro gráfico de dominio)
- .Cascade.AllDeleteOrphan () utilizado en la mayoría de los casos.
- Los usuarios poseen exclusivamente algunos objetos en el gráfico de dominio, pero comparten la propiedad de Parent.
- Las propiedades de navegación en los objetos para niños no existen.
- Todas las clases tienen campos numéricos de Id. Y Versión numérica.
Caso de uso:
- El usuario 1 inicia la aplicación y abre Parent.
- El usuario 2 inicia la aplicación y abre Parent.
- El usuario 2 agrega un niño (en este documento C2).
- El usuario 2 guarda Parent.
- El usuario 1 agrega un niño (en este caso C1).
- El usuario 1 guarda Parent.
- El usuario 1 recibe una StaleObjectStateException (y con razón)
Queremos manejar con gracia la excepción. Dado que los usuarios comparten la propiedad del elemento primario, el usuario 1 debería poder guardarlo correctamente y guardar al padre con su hijo nuevo y el hijo del usuario 2.
Cuando se arroja SOSE, según Ayende ( http://msdn.microsoft.com/en-us/magazine/ee819139.aspx ):
su sesión y sus entidades cargadas son pan comido, porque con NHibernate, una excepción lanzada desde una sesión mueve esa sesión a un estado indefinido. Ya no puedes usar esa sesión o cualquier entidad cargada
A C1 ya se le ha asignado una identificación y un número de versión en la sesión que ahora no es útil. (Desearía que no hubiera sido así)
¿Cómo combinamos el uso de ISession.Merge () e ISession.Refresh () para obtener un Parent recientemente guardado que tenga C1 y C2?
Hemos probado varias permutaciones arcanas, ninguna de las cuales funciona completamente. Por lo general, una "transacción fue actualizada o eliminada por otra transacción (o la asignación de valores no guardados era incorrecta" o una colisión de identificación real en el nivel ODBC).
Nuestra teoría, por el momento:
- Restablezca los números de versión en C1 (para evitar que "la asignación de valores no guardados sea incorrecta")
- Obtener una nueva sesión
- newSession.Refresh (C1);
- newParent = newSession.QueryOver [...]
- newParent.Add (C1);
- newSession.SaveOrUpdate (newParent)
Sin embargo, toda la documentación sugiere que se supone que newSession.Merge es suficiente.
Otros mensajes utilizados como investigación:
Nuevo usuario de NHibernate: Fila fue actualizada o eliminada por otra transacción
¿Existe una alternativa a ISession.Merge () que no arroja cuando se usa el bloqueo optimista?
La fila StaleObjectstateException fue actualizada o eliminada por
Cómo puedo decirle a NHibernate que solo guarde las propiedades modificadas
Hibernate (JPA): cómo manejar StaleObjectStateException cuando varios objetos han sido modificados y comprometidos (java, pero relevante, creo)
Dado que los usuarios comparten la propiedad del elemento primario, el usuario 1 debería poder guardarlo correctamente y guardar al padre con su hijo nuevo y el hijo del usuario 2.
¿Por qué no simplemente deshabilita el bloqueo optimista en la colección secundaria? Entonces, cualquiera puede agregar hijos y no aumentará la versión del padre.
De lo contrario, esta es la solución que mi proyecto actual utiliza para todas las excepciones recuperables que una sesión podría arrojar (por ejemplo, conexión a DB perdido, clave externa violada, ...):
- Antes de llamar a
session.Flush()
la sesión se serializa en unMemoryStream
. - Si
session.Flush()
otransaction.Commit()
arroja una excepción que es recuperable, la sesión original se elimina y la guardada se deserializa. - La pantalla de llamada obtiene la información de que la sesión se recuperó después de una excepción y vuelve a llamar a las mismas consultas a las que se llamó cuando se abrió la pantalla la primera vez. Y debido a que todas las entidades modificadas todavía están en la sesión recuperada, el usuario ahora tiene el estado justo antes de presionar guardar.