Choosing NHibernate StatelessSession vs ISession for Bulk Insert Operations
Decide when to use StatelessSession for high-volume inserts and when the full ISession is needed, with a comparison table, trade-offs, and a sample implementation.
27 Aug 2026, 20:24 UTC

Decision and constraints
When you need to insert a large number of entities with NHibernate, you must choose between the full-featured ISession and the lightweight StatelessSession. The decision hinges on whether you require change tracking, lazy loading, or NHibernate event listeners. If those features are unnecessary and you want to minimise overhead, StatelessSession is the preferred option. This guide assumes NHibernate 5.x running on .NET Framework 4.6+ or .NET Core 3.1+.
Option comparison
| Feature | StatelessSession | ISession |
|---|---|---|
| Change tracking | No | Yes |
| Lazy loading | Not supported | Supported |
| Event listeners (IInterceptor, IEventListener) | Not invoked | Invoked |
| Batch size control | Explicit via ADO.NET batching | Implicit via flush mode |
| Typical throughput (approx.) | 2-3x higher than ISession | Baseline |
Trade-offs
Using StatelessSession reduces memory pressure and eliminates the flush overhead that comes with dirty checking. However, you lose:
- Automatic dirty checking - you must manage entity state yourself.
- Cascade operations - saves or deletes of associated entities are not propagated.
- NHibernate lifecycle events - any auditing, validation, or custom logic in interceptors or listeners will not fire.
- Lazy-loaded properties - accessing them after an insert throws
LazyInitializationExceptionunless you explicitly fetch them.
If you need any of the above, stay with ISession and tune its flush mode or use batching judiciously.
Concrete implementation
The following snippet shows a bulk insert using StatelessSession with explicit ADO.NET batching. Adjust the batchSize and connection string to your environment.
using (var stateless = sessionFactory.OpenStatelessSession())
{
using (var tx = stateless.BeginTransaction())
{
// Enable ADO.NET batching (requires proper driver configuration)
for (int i = 0; i < batchSize; i++)
{
var entity = new Entity
{
// set properties as needed
Name = $"Item {i}",
Value = i
};
stateless.Insert(entity);
}
tx.Commit();
}
}
To verify that no interceptors fire, place a log statement or breakpoint in your IInterceptor.OnSave implementation; it should remain untouched when the above code runs.
Limitations and validation
Because StatelessSession bypasses change tracking, you must ensure that:
- All required columns are populated before calling
Insert. - Any business logic that relied on
ISessionevents is replicated manually. - You do not later attempt to lazy-load properties on the inserted instances without first fetching them.
A practical way to check the result is to run a benchmark inserting a known number of rows (e.g., 100,000) with both ISession (FlushMode.Auto) and StatelessSession, measuring elapsed time and memory consumption. You should observe the stateless variant completing faster and using less memory, while a SQL profiler shows only INSERT statements (batched if configured) and no extra SELECTs for change tracking.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.