-
Notifications
You must be signed in to change notification settings - Fork 933
-
Anyone use NHibernate with Blazor Server? If so, how are you handling ISession and razor component lifetimes?
Beta Was this translation helpful? Give feedback.
All reactions
Replies: 1 comment
-
I found your post awhile ago when struggling through using ISession in a Blazor Server app. I still haven't found anyone talking about it online, so I'll post my solution here. I had to implement my own session management in a .razor
component, so it's hard to explain in a few sentences. Please forgive the length of this post.
An ISession is an inexpensive, non-threadsafe object that should be used once, for a single business process, and then discarded.
https://nhibernate.info/doc/nhibernate-reference/transactions.html
Blazor injects scoped services, which live for the lifetime of the SPA (until reload). In our experience, injecting an ISession into each page is not feasible, because Blazor is asynchronous. This means that it will often run methods that both utilize the same ISession to call the database in parallel, which will result in a concurrent session error.
A solution to this would be to inject the ISessionFactory
instead and wrap each transaction in an OpenSession()
.
using (var session = sessionFactory.OpenSession()) using (var transaction = session.BeginTransaction()) { ...
Since we wrap the session in a using
block, the session will get closed and disposed at the end of that unit of work.
NHibernate uses LazyLoading by default, which is great for performance. You can't lazy load an object if the session is already closed. This means that we cannot lazy-load anything in Blazor, because we dispose of the session before the UI renders. To use this method, we must use a lot of Fetch
statements in our query, and potentially turn off LazyLoading to offset the amount of required Fetch
statements. This is not ideal.
A solution to this problem would be to not dispose the session; rather we leave it open so that when Blazor asynchronously updates the UI, the session is still open so that the object in question may be LazyLoaded on demand.
var session = sessionFactory.OpenSession(); using (var transaction = session.BeginTransaction()) {
There is an obvious issue with this: each time the routine encapsulating a database query is called, it opens a new session which will never be disposed. LazyLoading will work, however.
The solution to this issue is to implement our own session management. Track each open session in a List<ISession>
and implement the IDisposable
interface on each Blazor page so that when the user navigates to another page, it will close and delete all of the sessions. Each session needs to be open while any updates render on the page, but it can be closed after the render.
@using NHibernate @implements IDisposable @code { public NHibernate.ISessionFactory sessionFactory { get; set; } public List<NHibernate.ISession> sessions { get; set; } /// <summary> /// Get the sessionFactory /// </summary> protected override void OnInitialized() /// <summary> /// Dispose all sessions with closed transactions after rendering /// This allows sessions to remain open long enough to lazy-load during rendering /// </summary> /// <param name="firstRender"></param> protected override void OnAfterRender(bool firstRender) /// <summary> /// Open a new session and maintain an internal handle to it /// </summary> /// <param name="protect">If true, the session will not be automatically discarded.</param> /// <returns>An open NHibernate ISession</returns> public NHibernate.ISession GetSession(bool protect = false) /// <summary> /// Dispose of all remaining sessions when navigating to another page /// </summary> public void Dispose() }
Every page in my Blazor application that needs to do a database transaction inherits from the above component. I can share my implementation of the stub above if anyone is interested. Here is a simple example of how to use it.
@inherits NhibernateBase ... var session = GetSession(); using (var transaction = session.BeginTransaction()) { MyList = await session.Query<MyClass>().ToListAsync(); await transaction.CommitAsync(); }
Beta Was this translation helpful? Give feedback.