2010-08-26 15 views
5

Primero, algunos antecedentes: soy nuevo en ASP.NET MVC 2 y NHibernate. Estoy comenzando mi primera aplicación y quiero usar NHibernate, porque vengo de las aplicaciones web JSP + Struts 1 + Hibernate.Cuándo realizar transacciones NHibernate en la aplicación ASP.NET MVC 2?

Nadie parece estar hablando de esto, así que supongo que debe ser bastante obvio. Todavía me rasco la cabeza porque no puedo encontrar una solución que logre lo siguiente:

1) Quiero utilizar la estrategia de "sesión por solicitud". Por lo tanto, cada vez que un usuario realiza una solicitud, obtiene una sesión de Nhibernate, inicia una transacción y, cuando finaliza la solicitud, la transacción se compromete y la sesión de NHibernate se cierra (y regresa al grupo si hay una). Esto garantiza que mis transacciones sean atómicas.

2) Cuando se produce una excepción de base de datos (violación PK, violación única, lo que sea) Quiero capturar esa excepción, deshacer mi transacción y darle al usuario un mensaje explícito: si fue violación PK, entonces ese mensaje y el lo mismo con todos los errores de integridad.

Entonces, ¿cuál es mi problema? Vengo de Java World, donde utilicé un filtro para abrir la sesión, iniciar la transacción, procesar la solicitud, luego comprometer la transacción y cerrar la sesión. Esto funciona, excepto cuando se produce una excepción de base de datos, y cuando se encuentra en el filtro no hay forma de cambiar la página de destino porque la respuesta ya está confirmada.

Por lo tanto, el usuario ve la página de éxito cuando en realidad se canceló la transacción. Para evitar esto, tengo que escribir muchas comprobaciones de integridad de datos en Java para evitar todas las excepciones de integridad, porque no puedo manejarlas correctamente. Esto es malo porque estoy haciendo el trabajo en lugar de dejarlo en la base de datos (¿o quizás estoy equivocado y siempre tengo que escribir todo este código de integridad de datos en mi aplicación?).

Así que he encontrado la interfaz IHttpModule, que supongo que es más o menos el mismo concepto que un javax.servlet.Filter (corríjame si me equivoco), así que supongo que podría tener el mismo problema de nuevo.

¿Dónde debo poner mis confirmaciones para asegurarme de que mis transacciones son atómicas y cuando arrojan excepciones puedo capturarlas, cambiar mi página de destino y darle al usuario un mensaje completo?

Hasta ahora, la única solución posible que he encontrado es mantener mi IHttpModule para iniciar y cerrar la transacción, y poner las llamadas de confirmación en la última línea de mis métodos de control, pudiendo así capturar excepciones allí y luego devuelve una vista apropiada con el mensaje. Ahora tendría que copiar esas líneas de confirmación y manejo de excepciones en todos mis métodos de controlador que requieren confirmaciones. Y está la cuestión de la separación de las preocupaciones, que mis controladores tienen que saber sobre DB, que no me gusta para nada.

¿Hay una manera mejor?

Respuesta

0

Bueno, después de pensar en ello y discutido con los compañeros de trabajo que he llegado a una solución que cumple con casi todas mis necesidades .

Implementé la solución con mis proyectos Java y funcionó muy bien. Voy a depurar la idea para que todos puedan usarla en cualquier marco.

La solución consiste en poner la llamada de confirmación en la última línea del método del controlador, dentro de un bloque try-catch. Si se produce una excepción de restricción, puede obtener el nombre de la restricción violada. Con el nombre, puede decirle al usuario exactamente qué salió mal. Usé un archivo de propiedades para almacenar el mensaje para mostrarle al usuario qué restricción se violó. Las claves del archivo de propiedades son los nombres de restricciones y los valores son los mensajes de violación de restricciones.

Yo puedo refactorizar commit-handle_exception-find_constraint_message a un método, eso es lo que hice.

Por ahora resuelve mi problema de escribir código para verificar la integridad de la base de datos y creo que es bastante elegante con los mensajes de violación de restricciones en un archivo de propiedades. Ahora, todavía no me gusta la idea de que mis controladores necesiten llamar al compromiso, pero eso es mucho mejor que escribir verificaciones de integridad que la base de datos ya hace.

Seguiré usando un filtro como dijo David Kemp, solo que el filtro solo abrirá la (n) sesión de hibernación y la transacción, y luego, al final de la solicitud, cerrará la sesión.

Los comentarios son más que bienvenidos. Gracias.

+0

¿Puedes mostrar un ejemplo en el código? Eso sería útil. –

1

Si está utilizando ASP.NET MVC, puede usar un ActionFilter para lograr el mismo efecto.

Algo así (esto se cortó a partir de piezas de diferencia de mi arquitectura):

public class TransactionalAttribute : ActionFilterAttribute, IAuthorizationFilter, IExceptionFilter 
{ 

    ITransaction transaction = NullTransaction.Instance; 
    public IsolationLevel IsolationLevel { get; set; } 

    public TransactionalAttribute() 
    { 
     IsolationLevel = IsolationLevel.ReadCommitted; 
    } 

    public override void OnResultExecuted(ResultExecutedContext filterContext) 
    { 
     try 
     { 
      transaction.Commit(); 
      transaction = NullTransaction.Instance; 
     } 
     catch (Exception exception) 
     { 
      Log.For(this).FatalFormat("Problem trying to commit transaction {0}", exception); 
     } 

    } 

    public override void OnActionExecuting(ActionExecutingContext filterContext) 
    { 
     if (transaction == NullTransaction.Instance) transaction = UnitOfWork.Current.BeginTransaction(IsolationLevel); 
    } 

    public override void OnActionExecuted(ActionExecutedContext filterContext) 
    { 
     if (filterContext.Result != null) return; 

     transaction.Commit(); 
     transaction = NullTransaction.Instance; 
    } 

    public void OnAuthorization(AuthorizationContext filterContext) 
    { 
     transaction = UnitOfWork.Current.BeginTransaction(IsolationLevel); 
    } 

    public void OnException(ExceptionContext filterContext) 
    { 
     try 
     { 
      transaction.Rollback(); 
      transaction = NullTransaction.Instance; 
     } 
     catch (Exception exception) 
     { 
      Log.For(this).FatalFormat("Problem trying to rollback transaction {0}", exception); 
     } 
    } 

    private class NullTransaction : ITransaction 
    { 
     public static ITransaction Instance { get { return Singleton<NullTransaction>.Instance; } } 

     public void Dispose() 
     { 

     } 

     public void Commit() 
     { 
     } 

     public void Rollback() 
     { 
     } 
    } 
} 
Cuestiones relacionadas