Description
Describe the bug
Intermittent NullReferenceException thrown at NHibernate.Connection.ConnectionProvider.CloseConnection(DbConnection conn)
Steps to reproduce
I unfortunately don't have a simple standalone project to reproduce this issue. I see it from time to time when running regression tests.
Expected behavior
- No null reference exception should occur.
Environment
- Windows 10
- NHibernate 5.2.7
- Oracle.ManagedDataAccess 18.6.0
AdditionalContext
Full stack trace below, together with my (condensed) code for UnitOfWork - credits to Vladimir Khorikov - and SessionFactory construction. Mine is a multi-threaded application. The SessionFactory is created one and used across the app. Multiple sessions may be opened simultaneously using the same connection string, but session usage always occurs within a single unit of work, and a new unit of work is always newed up for a given command (i.e. I don't believe the same unit of work can be used across multiple threads). If I've misunderstood the documentation regarding session usage in a multithreaded environment, please help me understand my error.
Message: System.Exception : Exception=Object reference not set to an instance of an object.
StackTrace=
at NHibernate.Connection.ConnectionProvider.CloseConnection(DbConnection conn)
at NHibernate.Connection.DriverConnectionProvider.CloseConnection(DbConnection conn)
at NHibernate.Transaction.AdoNetTransactionFactory.ExecuteWorkInIsolation(ISessionImplementor session, IIsolatedWork work, Boolean transacted)
at NHibernate.Transaction.AdoNetWithSystemTransactionFactory.ExecuteWorkInIsolation(ISessionImplementor session, IIsolatedWork work, Boolean transacted)
at NHibernate.Engine.Transaction.Isolater.DoIsolatedWork(IIsolatedWork work, ISessionImplementor session)
at NHibernate.Engine.TransactionHelper.DoWorkInNewTransaction(ISessionImplementor session)
at NHibernate.Id.TableGenerator.Generate(ISessionImplementor session, Object obj)
at NHibernate.Id.TableHiLoGenerator.Generate(ISessionImplementor session, Object obj)
at NHibernate.Event.Default.AbstractSaveEventListener.SaveWithGeneratedId(Object entity, String entityName, Object anything, IEventSource source, Boolean requiresImmediateIdAccess)
at NHibernate.Event.Default.DefaultSaveOrUpdateEventListener.SaveWithGeneratedOrRequestedId(SaveOrUpdateEvent event)
at NHibernate.Event.Default.DefaultSaveOrUpdateEventListener.EntityIsTransient(SaveOrUpdateEvent event)
at NHibernate.Event.Default.DefaultSaveOrUpdateEventListener.PerformSaveOrUpdate(SaveOrUpdateEvent event)
at NHibernate.Event.Default.DefaultSaveOrUpdateEventListener.OnSaveOrUpdate(SaveOrUpdateEvent event)
at NHibernate.Impl.SessionImpl.FireSaveOrUpdate(SaveOrUpdateEvent event)
at NHibernate.Impl.SessionImpl.SaveOrUpdate(String entityName, Object obj)
at NHibernate.Engine.CascadingAction.SaveUpdateCascadingAction.Cascade(IEventSource session, Object child, String entityName, Object anything, Boolean isCascadeDeleteEnabled)
at NHibernate.Engine.Cascade.CascadeToOne(Object parent, Object child, IType type, CascadeStyle style, Object anything, Boolean isCascadeDeleteEnabled)
at NHibernate.Engine.Cascade.CascadeAssociation(Object parent, Object child, IType type, CascadeStyle style, Object anything, Boolean isCascadeDeleteEnabled)
at NHibernate.Engine.Cascade.CascadeProperty(Object parent, Object child, IType type, CascadeStyle style, String propertyName, Object anything, Boolean isCascadeDeleteEnabled)
at NHibernate.Engine.Cascade.CascadeCollectionElements(Object parent, Object child, CollectionType collectionType, CascadeStyle style, IType elemType, Object anything, Boolean isCascadeDeleteEnabled)
at NHibernate.Engine.Cascade.CascadeCollection(Object parent, Object child, CascadeStyle style, Object anything, CollectionType type)
at NHibernate.Engine.Cascade.CascadeAssociation(Object parent, Object child, IType type, CascadeStyle style, Object anything, Boolean isCascadeDeleteEnabled)
at NHibernate.Engine.Cascade.CascadeProperty(Object parent, Object child, IType type, CascadeStyle style, String propertyName, Object anything, Boolean isCascadeDeleteEnabled)
at NHibernate.Engine.Cascade.CascadeOn(IEntityPersister persister, Object parent, Object anything)
at NHibernate.Event.Default.AbstractSaveEventListener.CascadeAfterSave(IEventSource source, IEntityPersister persister, Object entity, Object anything)
at NHibernate.Event.Default.AbstractSaveEventListener.PerformSaveOrReplicate(Object entity, EntityKey key, IEntityPersister persister, Boolean useIdentityColumn, Object anything, IEventSource source, Boolean requiresImmediateIdAccess)
at NHibernate.Event.Default.AbstractSaveEventListener.PerformSave(Object entity, Object id, IEntityPersister persister, Boolean useIdentityColumn, Object anything, IEventSource source, Boolean requiresImmediateIdAccess)
at NHibernate.Event.Default.AbstractSaveEventListener.SaveWithGeneratedId(Object entity, String entityName, Object anything, IEventSource source, Boolean requiresImmediateIdAccess)
at NHibernate.Event.Default.DefaultSaveOrUpdateEventListener.SaveWithGeneratedOrRequestedId(SaveOrUpdateEvent event)
at NHibernate.Event.Default.DefaultSaveOrUpdateEventListener.EntityIsTransient(SaveOrUpdateEvent event)
at NHibernate.Event.Default.DefaultSaveOrUpdateEventListener.PerformSaveOrUpdate(SaveOrUpdateEvent event)
at NHibernate.Event.Default.DefaultSaveOrUpdateEventListener.OnSaveOrUpdate(SaveOrUpdateEvent event)
at NHibernate.Impl.SessionImpl.FireSaveOrUpdate(SaveOrUpdateEvent event)
at NHibernate.Impl.SessionImpl.SaveOrUpdate(String entityName, Object obj)
at NHibernate.Engine.CascadingAction.SaveUpdateCascadingAction.Cascade(IEventSource session, Object child, String entityName, Object anything, Boolean isCascadeDeleteEnabled)
at NHibernate.Engine.Cascade.CascadeToOne(Object parent, Object child, IType type, CascadeStyle style, Object anything, Boolean isCascadeDeleteEnabled)
at NHibernate.Engine.Cascade.CascadeAssociation(Object parent, Object child, IType type, CascadeStyle style, Object anything, Boolean isCascadeDeleteEnabled)
at NHibernate.Engine.Cascade.CascadeProperty(Object parent, Object child, IType type, CascadeStyle style, String propertyName, Object anything, Boolean isCascadeDeleteEnabled)
at NHibernate.Engine.Cascade.CascadeCollectionElements(Object parent, Object child, CollectionType collectionType, CascadeStyle style, IType elemType, Object anything, Boolean isCascadeDeleteEnabled)
at NHibernate.Engine.Cascade.CascadeCollection(Object parent, Object child, CascadeStyle style, Object anything, CollectionType type)
at NHibernate.Engine.Cascade.CascadeAssociation(Object parent, Object child, IType type, CascadeStyle style, Object anything, Boolean isCascadeDeleteEnabled)
at NHibernate.Engine.Cascade.CascadeProperty(Object parent, Object child, IType type, CascadeStyle style, String propertyName, Object anything, Boolean isCascadeDeleteEnabled)
at NHibernate.Engine.Cascade.CascadeOn(IEntityPersister persister, Object parent, Object anything)
at NHibernate.Event.Default.AbstractFlushingEventListener.CascadeOnFlush(IEventSource session, IEntityPersister persister, Object key, Object anything)
at NHibernate.Event.Default.AbstractFlushingEventListener.PrepareEntityFlushes(IEventSource session)
at NHibernate.Event.Default.AbstractFlushingEventListener.FlushEverythingToExecutions(FlushEvent event)
at NHibernate.Event.Default.DefaultFlushEventListener.OnFlush(FlushEvent event)
at NHibernate.Impl.SessionImpl.Flush()
at NHibernate.Impl.SessionImpl.FlushBeforeTransactionCompletion()
at NHibernate.Impl.SessionImpl.BeforeTransactionCompletion(ITransaction tx)
at NHibernate.Transaction.AdoTransaction.Commit()
at MyApp.Core.Domain.Core.CommandQueryHandling.UnitOfWork.Commit() in C:\src\NSG\MyApp\MyApp.Core\Domain\Core\CommandQueryHandling\UnitOfWork.cs:line 38
at MyApp.Core.Domain.State.Commands.AdvanceStateHistoryLegacy.AdvanceStateHistoryHandler.<>c__DisplayClass3_1.<Handle>b__6() in C:\src\NSG\MyApp\MyApp.Core\Domain\State\Commands\AdvanceStateHistoryLegacy.cs:line 56
at CSharpFunctionalExtensions.ResultExtensions.OnSuccess(Result result, Action action)
at MyApp.Core.Domain.State.Commands.AdvanceStateHistoryLegacy.AdvanceStateHistoryHandler.Handle(AdvanceStateHistoryLegacy command) in C:\src\NSG\MyApp\MyApp.Core\Domain\State\Commands\AdvanceStateHistoryLegacy.cs:line 53
at CallSite.Target(Closure , CallSite , Object , Object )
at System.Dynamic.UpdateDelegates.UpdateAndExecute2[T0,T1,TRet](CallSite site, T0 arg0, T1 arg1)
at CallSite.Target(Closure , CallSite , Object , Object )
at MyApp.Core.Domain.Core.CommandQueryHandling.Messages.Dispatch(ICommand command) in C:\src\NSG\MyApp\MyApp.Core\Domain\Core\CommandQueryHandling\Messages.cs:line 22
at MyApp.Core.CrossCutting.Extensions.ResultExtensions.Map[T](Result`1 result, Func`2 func) in C:\src\NSG\MyApp\MyApp.Core\CrossCutting\Extensions\ResultExtensions.cs:line 88
at MyApp.Core.Application.Runjobs.StoreMeasurements.StoreMeasurementsRunjob.GetNormalReply(StoreMeasurementsContext runjobCollaterals) in C:\src\NSG\MyApp\MyApp.Core\Application\Runjobs\StoreMeasurements\StoreMeasurementsRunjob.cs:line 61
at MyApp.Core.Application.Runjobs.StoreMeasurements.StoreMeasurementsRunjob.<>c__DisplayClass7_0.<Handle>b__3(IntroType introType) in C:\src\NSG\MyApp\MyApp.Core\Application\Runjobs\StoreMeasurements\StoreMeasurementsRunjob.cs:line 45
at MyApp.Core.CrossCutting.Extensions.ResultExtensions.Map[T,K](Result`1 result, Func`2 func) in C:\src\NSG\MyApp\MyApp.Core\CrossCutting\Extensions\ResultExtensions.cs:line 82
at MyApp.Core.Application.Runjobs.StoreMeasurements.StoreMeasurementsRunjob.Handle(XDocument runjob) in C:\src\NSG\MyApp\MyApp.Core\Application\Runjobs\StoreMeasurements\StoreMeasurementsRunjob.cs:line 45
at MyApp.Core.Application.Runjobs.GenericRunjob.<>c__DisplayClass7_0.<Handle>b__3(IClassifiedRunjob runjob) in C:\src\NSG\MyApp\MyApp.Core\Application\Runjobs\GenericRunjob.cs:line 42
at MyApp.Core.CrossCutting.Extensions.ResultExtensions.Map[T,K](Result`1 result, Func`2 func) in C:\src\NSG\MyApp\MyApp.Core\CrossCutting\Extensions\ResultExtensions.cs:line 82
at MyApp.Core.Application.Runjobs.GenericRunjob.Handle(XDocument xDoc) in C:\src\NSG\MyApp\MyApp.Core\Application\Runjobs\GenericRunjob.cs:line 39
at MyApp.Core.Application.Entry.NonComServiceRequest.<>c__DisplayClass4_1.<HandleRunjob>b__9(IRunjob`1 runjob) in C:\src\NSG\MyApp\MyApp.Core\Application\Entry\NonComServiceRequest.cs:line 40
at MyApp.Core.CrossCutting.Extensions.ResultExtensions.Map[T,K](Result`1 result, Func`2 func) in C:\src\NSG\MyApp\MyApp.Core\CrossCutting\Extensions\ResultExtensions.cs:line 82
at MyApp.Core.Application.Entry.NonComServiceRequest.HandleRunjob(String method, String requestXml) in C:\src\NSG\MyApp\MyApp.Core\Application\Entry\NonComServiceRequest.cs:line 37
public sealed class UnitOfWork : IDisposable
{
private readonly ISession _session;
private readonly ITransaction _transaction;
private bool _isAlive = true;
private bool _disposed = false;
public UnitOfWork(SessionFactory sessionFactory)
{
_session = sessionFactory.OpenSession();
_transaction = _session.BeginTransaction(IsolationLevel.ReadCommitted);
}
public void Commit()
{
if (!_isAlive)
return;
try
{
_transaction.Commit();
}
finally
{
_isAlive = false;
Dispose();
}
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
private void Dispose(bool disposing)
{
if (_disposed) return;
if (disposing)
{
_transaction.Dispose();
_session.Dispose();
}
_disposed = true;
}
}
private ISessionFactory DoBuildSessionFactory(ConnectionString connectionString)
{
_logger.LogWarning("Constructing session factory");
lock (_lockObject)
{
var dbConfig = OracleDataClientConfiguration.Oracle10
.ConnectionString(c => c.Is(connectionString))
.Driver<NHibernate.Driver.OracleManagedDataClientDriver>();
//.ShowSql();
dbConfig = ApplicationGlobals.DefaultSchema.Equals(ApplicationGlobals.WindowsUser) ? dbConfig : dbConfig.DefaultSchema(ApplicationGlobals.DefaultSchema);
FluentConfiguration configuration = Fluently.Configure()
.Database(dbConfig)
.Mappings(m =>
{
m.FluentMappings.AddFromAssembly(Assembly.GetExecutingAssembly()).AddConventions()/*.ExportTo(@"c:\temp\mappings")*/;
m.HbmMappings.AddFromAssembly(Assembly.GetExecutingAssembly());
})
.ExposeConfiguration(x =>
{
x.EventListeners.PostCommitUpdateEventListeners = new IPostUpdateEventListener[] { new EventListener() };
x.EventListeners.PostCommitInsertEventListeners = new IPostInsertEventListener[] { new EventListener() };
x.EventListeners.PostCommitDeleteEventListeners = new IPostDeleteEventListener[] { new EventListener() };
x.EventListeners.PostCollectionUpdateEventListeners = new IPostCollectionUpdateEventListener[] { new EventListener() };
x.DataBaseIntegration(dbi =>
{
dbi.Batcher<OracleDataClientBatchingBatcherFactory>();
dbi.Dialect<Oracle10gDialect>();
});
//x.SetInterceptor(new SqlStatementInterceptor());
});
try
{
var factory = configuration.BuildSessionFactory();
return factory;
}
catch (FluentConfigurationException e)
{
var sb = new StringBuilder();
var fullMessage = e.FullMessage();
sb.AppendLine($"Fatal error building sessionFactory: '{fullMessage}'.");
sb.AppendLine($"Potential reasons:");
foreach (var reason in e.PotentialReasons) sb.AppendLine(reason);
sb.AppendLine($"Executing assembly path = {Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location)}");
_logger.LogCritical(e, $"Fatal error building session factory: {fullMessage}");
throw new System.Exception(sb.ToString());
}
}