todas - ¿Cómo revertir la transacción nHibernate cuando se produce una excepción durante la solicitud que tiene Ninject para la gestión de sesiones?
try catch definicion (2)
Uso nHibernate para ORM y Ninject para IoC. Creo sesiones nHibernate por cada ámbito personalizado (que puede asumir es por solicitud). Comienzo la transacción en Activación. Confirmo la transacción en desactivación.
El problema es que si ocurre una excepción durante la solicitud, quiero retrotraer la transacción en lugar de comprometerla. ¿Alguna idea de cómo detectar (de una manera limpia, probablemente usando el Contexto de Ninject) que ha ocurrido una excepción?
Nota: No me preocupan las excepciones que pueden ocurrir en commit que puedo detectar fácilmente en el siguiente código y función.
protected void BindWithSessionWrapper<T>(Func<IContext, T> creationFunc) where T : ISessionWrapper
{
Bind<T>().ToMethod(creationFunc)
.InScope(x => new NinjectCustomScope()) // work in progress !!!
.OnActivation(t => t.Session.BeginTransaction(IsolationLevel.ReadCommitted))
.OnDeactivation((c, t) =>
{
t.Session.Transaction.Commit();
t.Session.Dispose();
});
}
Actualizar:
Seguí la sugerencia de @BatteryBackupUnit. Así que agregué lo siguiente al Error EventHandler:
Error += (s, e) =>
{
HttpContext.Current.Items["ErrorRaised"] = true;
};
Y modifiqué OnDeactivation para que se vea así:
OnDeactivation(t =>
{
if ((bool?)HttpContext.Current.Items["ErrorRaised"] == true)
t.Session.Transaction.Rollback();
else
t.Session.Transaction.Commit();
t.Session.Dispose();
});
Funciona bien, pero sería mejor si Ninject se ocuparía de esto al establecer una bandera en el contexto si ocurriera una excepción :)
¿Qué le IHTTPModule
implementar un IHTTPModule
y suscribirse al evento Error
? Como se describe aquí
En el controlador de eventos Error
, use System.Web.Mvc.DependencyResolver.Current.GetService(typeof (ISession))
para recuperar la sesión actual y deshacer la transacción.
Sin embargo, tenga en cuenta que, en caso de que la solicitud no use una sesión, se creará una, lo cual es bastante superfluo.
Puede hacer algo como verificar si se inició una transacción y solo luego revertirla. Pero aún crearías una sesión innecesariamente.
Puede mejorar aún más utilizando el controlador de eventos Error
para establecer un HttpContext.Current.Items
en HttpContext.Current.Items
, como
HttpContext.Current.Items["RollbackTransaction"] = true;
y luego usarlo en la OnDeactivation
de la sesión como:
.OnDeactivation((c, t) =>
{
if(HttpContext.Current.Items.Contains("RollbackTransaction"])
{
t.Session.Transaction.Rollback();
}
else
{
t.Session.Transaction.Commit();
}
t.Session.Dispose();
});
Tenga en cuenta que HttpContext
es thread local, eso significa que cuando se cambian los hilos puede ser null
o, en el peor de los casos, podría ser otro HttpContext
.
Tenga en cuenta también que no pude probarlo para que no funcione. Comentarios apreciados.
Pasar el estado a través de HttpContext no es aceptable para mí por dos razones.
- Problema de HttpContext: https://.com/a/12219078/656430 )
- Aprobar el estado parece pasar un estado global ( https://softwareengineering.stackexchange.com/questions/148108/why-is-global-state-so-evil )
Después de muchas pruebas y errores, creo que esta debería ser una solución: suponiendo que estamos trabajando en el proyecto WebApi, al tener una transacción de reversión para todas las acciones, una vez que lleguemos a la excepción, con Ninject:
- instale Ninject.Extension.Factory ( https://www.nuget.org/packages/Ninject.Extensions.Factory/ ), este es un paso muy importante para inyectar ISession en el alcance de la solicitud en los filtros.
utilice la siguiente configuración para enlazar
ISessionFactory
eISession
(hice uso de este ejemplo: Necesito un ejemplo simple de usar nhibernate + unidad de trabajo + patrón de repositorio + capa de servicio + ninject ), másISessionInRequestScopeFactory
Bind<ISessionFactory>().ToProvider<NhibernateSessionFactoryProvider>().InSingletonScope(); Bind<ISession>() .ToMethod(context => context.Kernel.Get<ISessionFactory>().OpenSession()) .InRequestScope(); // notice that we don''t need to call `BeginTransaction` at this moment Bind<ISessionInRequestScopeFactory>().ToFactory(); // you don''t need to make your implementation, the Ninject.Extension.Factory extension will help you so.
el código para la interfaz
ISessionInRequestScopeFactory
:public interface ISessionInRequestScopeFactory { ISession CreateSessionInRequestScope(); // return ISession in the request scope }
Utilice la inyección de filtro ninject para agregar el comportamiento de transacción a cada acción ( https://github.com/ninject/Ninject.Web.WebApi/wiki/Dependency-injection-for-filters ):
Kernel.BindHttpFilter<ApiTransactionFilter>(System.Web.Http.Filters.FilterScope.Action) .WhenControllerHas<ApiTransactionAttribute>();
agrega el atributo
[ApiTransaction]
al controlador:[ApiTransaction] public class YourApiController{ /* ... */}
Así que ahora estamos vinculando el
ApiTransactionFilter
enYourApiController
que están teniendo[ApiTransaction]
AttributeDentro de
ApiTransactionFilter
, debe extenderAbstractActionFilter
e inyectar la fábricaISessionInRequestScopeFactory
para obtener la sesión de ámbito de solicitud correcta :public class ApiTransactionFilter : AbstractActionFilter{ private readonly ISessionInRequestScopeFactory factory; public ApiTransactionFilter(ISessionInRequestScopeFactory factory){ this.factory = factory; } public override void OnActionExecuting(HttpActionContext actionContext) { ISession session = factory.CreateSessionInRequestScope(); // get the request scope session through factory session.BeginTransaction(); // session can begin transaction here ... base.OnActionExecuting(actionContext); } public override void OnActionExecuted(HttpActionExecutedContext actionExecutedContext) { ISession session = factory.CreateSessionInRequestScope(); // get the request scope session through factory if (actionExecutedContext.Exception == null) // NO EXCEPTION! { session.Transaction.Commit();// session commit here ... may be you like to have try catch here } else { session.Transaction.Rollback(); // session rollback here ... } base.OnActionExecuted(actionExecutedContext); } }