NHibernate ISession in ASP.NET Core: DI scope vs CurrentSessionContext — which should own the session?
0 reputation · 29 Oct 2023, 20:01 UTC
0 reputation · 29 Oct 2023, 20:01 UTC
I'm integrating NHibernate 5.4 with ASP.NET Core 8 and need to decide how the ISession lifecycle should be managed. NHibernate's built-in CurrentSessionContext implementations (web, call) predate ASP.NET Core's middleware pipeline and IServiceScope model, so the two boundaries don't line up cleanly.
The community NHibernate.AspNetCore package registers ISessionFactory as a singleton and ISession as scoped, but it also binds sessions to CurrentSessionContext via middleware. That means a constructor-injected ISession and sessionFactory.GetCurrentSession() could resolve to different instances if middleware ordering or scope handling is off — which would break lazy-loading proxies and change tracking in subtle ways.
I want one authoritative session per HTTP request, shared by repositories that inject ISession and legacy code that calls GetCurrentSession(). I'm unsure whether to abandon CurrentSessionContext entirely or write a custom ICurrentSessionContext that delegates to IHttpContextAccessor.HttpContext.RequestServices.
ISession and GetCurrentSession() guaranteed to be the same instance under AddNHibernate(), or only under specific middleware ordering?ICurrentSessionContext delegating to the DI scope a sound approach, and are there documented pitfalls?31775 reputation · 29 Oct 2023, 20:14 UTC
To ensure a single authoritative ISession per HTTP request that is shared between constructor injection and legacy GetCurrentSession() calls, you should delegate the ICurrentSessionContext to the DI scope. Relying on the default NHibernate.AspNetCore middleware alone is risky because the identity of the session depends strictly on middleware execution order relative to service resolution.
In a standard AddNHibernate() setup, the DI container manages the ISession as a scoped service. Simultaneously, NHibernate's CurrentSessionContext manages a session in a separate internal storage (typically AsyncLocal). If the middleware that binds these two does not execute before your repositories are resolved, GetCurrentSession() may instantiate a second session, leading to SessionException errors during lazy loading or detached entity issues.
The most robust approach is to implement a custom ICurrentSessionContext that resolves the session directly from the IHttpContextAccessor. This guarantees that GetCurrentSession() always returns the exact instance managed by the ASP.NET Core DI container.
ICurrentSessionContext that uses IHttpContextAccessor.HttpContext.RequestServices.GetService<ISession>() to return the current session.cfg.CurrentSessionContext = new YourCustomContext() during the SessionFactory build process.Because this approach ties the session to the HttpContext, it will fail in background services (e.g., IHostedService) where HttpContext` is null. For non-request work, you must:
IServiceScope.ISession from that scope.GetCurrentSession() in background threads unless you have manually bound the session to a scope-aware context.To verify the configuration is correct, perform the following check within a Controller action:
public IActionResult VerifySession(ISession injectedSession, ISessionFactory sessionFactory)
{
var currentSession = sessionFactory.GetCurrentSession();
bool isSameInstance = ReferenceEquals(injectedSession, currentSession);
return Ok(new { isSameInstance });
}
If isSameInstance is false, your CurrentSessionContext is operating independently of the DI container.
Use comments to ask for clarification. Post a solution as an answer.
No question comments on this page.