/// <summary> /// Prepare the session for the transaction commit. Run /// <see cref="ISessionImplementor.BeforeTransactionCompletion(ITransaction)"/> for the session and for /// <see cref="ConnectionManager.DependentSessions"/> if any. <see cref="Lock"/> the context /// before signaling it is done, or before rollback in case of failure. /// </summary> /// <param name="preparingEnlistment">The object for notifying the prepare phase outcome.</param> public virtual void Prepare(PreparingEnlistment preparingEnlistment) { _preparing = true; using (_session.BeginContext()) { try { using (_session.ConnectionManager.BeginProcessingFromSystemTransaction(_useConnectionOnSystemTransactionPrepare)) { if (_useConnectionOnSystemTransactionPrepare) { // Ensure any newly acquired connection gets enlisted in the transaction. When distributed, // this code runs from another thread and we cannot rely on Transaction.Current. using (var tx = new TransactionScope(EnlistedTransaction)) { // Required when both connection auto-enlistment and session auto-enlistment are disabled. _session.JoinTransaction(); _session.BeforeTransactionCompletion(null); foreach (var dependentSession in _session.ConnectionManager.DependentSessions) { dependentSession.BeforeTransactionCompletion(null); } tx.Complete(); } } else { _session.BeforeTransactionCompletion(null); foreach (var dependentSession in _session.ConnectionManager.DependentSessions) { dependentSession.BeforeTransactionCompletion(null); } } } // Lock the session to ensure second phase gets done before the session is used by code following // the transaction scope disposal. Lock(); _logger.Debug("Prepared for system transaction"); _preparing = false; preparingEnlistment.Prepared(); } catch (Exception exception) { _preparing = false; _logger.Error(exception, "System transaction prepare phase failed"); try { CompleteTransaction(false); } finally { preparingEnlistment.ForceRollback(exception); } } } }
/// <inheritdoc /> public virtual Task ExecuteWorkInIsolationAsync(ISessionImplementor session, IIsolatedWork work, bool transacted, CancellationToken cancellationToken) { if (session == null) { throw new ArgumentNullException(nameof(session)); } if (work == null) { throw new ArgumentNullException(nameof(work)); } if (cancellationToken.IsCancellationRequested) { return(Task.FromCanceled <object>(cancellationToken)); } return(InternalExecuteWorkInIsolationAsync()); async Task InternalExecuteWorkInIsolationAsync() { DbConnection connection = null; DbTransaction trans = null; try { // We make an exception for SQLite and use the session's connection, // since SQLite only allows one connection to the database. connection = session.Factory.Dialect is SQLiteDialect ? session.Connection : await(session.Factory.ConnectionProvider.GetConnectionAsync(cancellationToken)).ConfigureAwait(false); if (transacted) { trans = connection.BeginTransaction(); } await(work.DoWorkAsync(connection, trans, cancellationToken)).ConfigureAwait(false); if (transacted) { trans.Commit(); } } catch (Exception t) { using (session.BeginContext()) { try { if (trans != null && connection.State != ConnectionState.Closed) { trans.Rollback(); } } catch (Exception ignore) { _isolatorLog.Debug(ignore, "Unable to rollback transaction"); } switch (t) { case HibernateException _: throw; case DbException _: throw ADOExceptionHelper.Convert(session.Factory.SQLExceptionConverter, t, "error performing isolated work"); default: throw new HibernateException("error performing isolated work", t); } } } finally { try { trans?.Dispose(); } catch (Exception ignore) { _isolatorLog.Warn(ignore, "Unable to dispose transaction"); } if (connection != null && session.Factory.Dialect is SQLiteDialect == false) { session.Factory.ConnectionProvider.CloseConnection(connection); } } } }
/// <inheritdoc /> public virtual Task ExecuteWorkInIsolationAsync(ISessionImplementor session, IIsolatedWork work, bool transacted, CancellationToken cancellationToken) { if (session == null) { throw new ArgumentNullException(nameof(session)); } if (work == null) { throw new ArgumentNullException(nameof(work)); } if (cancellationToken.IsCancellationRequested) { return(Task.FromCanceled <object>(cancellationToken)); } return(InternalExecuteWorkInIsolationAsync()); async Task InternalExecuteWorkInIsolationAsync() { DbConnection connection = null; DbTransaction trans = null; // bool wasAutoCommit = false; try { // We make an exception for SQLite and use the session's connection, // since SQLite only allows one connection to the database. if (session.Factory.Dialect is SQLiteDialect) { connection = session.Connection; } else { connection = await(session.Factory.ConnectionProvider.GetConnectionAsync(cancellationToken)).ConfigureAwait(false); } if (transacted) { trans = connection.BeginTransaction(); // TODO NH: a way to read the autocommit state is needed //if (TransactionManager.GetAutoCommit(connection)) //{ // wasAutoCommit = true; // TransactionManager.SetAutoCommit(connection, false); //} } await(work.DoWorkAsync(connection, trans, cancellationToken)).ConfigureAwait(false); if (transacted) { trans.Commit(); //TransactionManager.Commit(connection); } } catch (Exception t) { using (session.BeginContext()) { try { if (trans != null && connection.State != ConnectionState.Closed) { trans.Rollback(); } } catch (Exception ignore) { isolaterLog.Debug(ignore, "Unable to rollback transaction"); } if (t is HibernateException) { throw; } else if (t is DbException) { throw ADOExceptionHelper.Convert(session.Factory.SQLExceptionConverter, t, "error performing isolated work"); } else { throw new HibernateException("error performing isolated work", t); } } } finally { //if (transacted && wasAutoCommit) //{ // try // { // // TODO NH: reset autocommit // // TransactionManager.SetAutoCommit(connection, true); // } // catch (Exception) // { // log.Debug("was unable to reset connection back to auto-commit"); // } //} try { trans?.Dispose(); } catch (Exception ignore) { isolaterLog.Warn(ignore, "Unable to dispose transaction"); } if (session.Factory.Dialect is SQLiteDialect == false) { session.Factory.ConnectionProvider.CloseConnection(connection); } } } }
/// <inheritdoc /> public virtual void ExecuteWorkInIsolation(ISessionImplementor session, IIsolatedWork work, bool transacted) { if (session == null) { throw new ArgumentNullException(nameof(session)); } if (work == null) { throw new ArgumentNullException(nameof(work)); } DbConnection connection = null; DbTransaction trans = null; try { // We make an exception for SQLite and use the session's connection, // since SQLite only allows one connection to the database. connection = session.Factory.Dialect is SQLiteDialect ? session.Connection : session.ConnectionManager.GetNewConnection(); if (transacted) { trans = connection.BeginTransaction(); } work.DoWork(connection, trans); if (transacted) { trans.Commit(); } } catch (Exception t) { using (session.BeginContext()) { try { if (trans != null && connection.State != ConnectionState.Closed) { trans.Rollback(); } } catch (Exception ignore) { _isolatorLog.Debug(ignore, "Unable to rollback transaction"); } switch (t) { case HibernateException _: throw; case DbException _: throw ADOExceptionHelper.Convert(session.Factory.SQLExceptionConverter, t, "error performing isolated work"); default: throw new HibernateException("error performing isolated work", t); } } } finally { try { trans?.Dispose(); } catch (Exception ignore) { _isolatorLog.Warn(ignore, "Unable to dispose transaction"); } if (connection != null && session.Factory.Dialect is SQLiteDialect == false) { session.Factory.ConnectionProvider.CloseConnection(connection); } } }