Skip to content

Rollback

This operation reverses the unit of work's transaction and — unlike Commit — tears down the session afterward on the normal (non-throwing) path, since there is no try/finally guarding the disposal call.

Field Value
Operation ID OP-NH-09
Trigger Consumer calls IUnitOfWork.Rollback()
Inputs —
Outputs Transaction rolled back if it was active; session disposed only when transaction.Rollback() itself does not throw
Primary components UnitOfWork.Rollback
Entry point src/SAIF.NetCore.Nhibernate/SAIF.NetCore.NHibernate/SAIF.NetCore.NHibernate/UnitOfWork.cs

Steps

  1. Read Session.Transaction().
  2. If transaction.IsActive is true: call transaction.Rollback().
  3. If transaction.IsActive is false: do nothing here — unlike Commit, Rollback does not throw when no transaction is active.
  4. If transaction.Rollback() did not throw, call Session.Dispose() next. There is no try/finally around the rollback call: if transaction.Rollback() itself throws, Session.Dispose() is skipped entirely and the exception propagates to the caller — Rollback() provides no exception-safety guarantee that cleanup always runs (BR-NH-14).
  5. Rollback() does not set the isDisposed flag — only UnitOfWork.Dispose()/Dispose(bool) does that. Calling Rollback() and later Dispose() still runs the full disposal logic in Dispose unit of work; disposal is not skipped just because Rollback() ran first.

Process flow

The diagram below visualizes the active-transaction check, the rollback-throws vs. rollback-succeeds paths, and the session-disposal step that only the non-throwing path reaches.

flowchart TD
    Start(["Rollback() called"]) --> Check{"transaction.IsActive?"}
    Check -->|Yes| DoRollback["transaction.Rollback()"]
    Check -->|No| Skip["No rollback performed<br>(no exception thrown)"]
    DoRollback -->|"throws"| Propagate(["Exception propagates;<br>Session.Dispose() skipped"])
    DoRollback -->|"succeeds"| DisposeSession["Session.Dispose()"]
    Skip --> DisposeSession
    DisposeSession --> Return(["Session disposed on this path"])

Failure and edge-case behavior

  • Calling Rollback() with no active transaction does not throw — it still proceeds to dispose the session (per step 3). This is asymmetric with Commit, which throws in that same situation.*
  • When transaction.Rollback() succeeds (or no rollback was needed), Session.Dispose() runs. Whether a subsequent call on the unit of work fails is not guaranteed by this page: the stateful session strategy's Session getter can transparently reopen a closed-but-not-fully-disposed session (see ISessionStrategy), so "the session is now closed" does not necessarily mean every further call fails — the exact behavior of further calls after disposal depends on strategy and is not fully characterized here.**
  • When transaction.Rollback() throws, Session.Dispose() is skipped (no try/finally) and the exception propagates before any disposal occurs. Whether the session is consequently left in a reusable or broken state is not confirmed by this library's own source — this library does not prove either outcome.**
  • Rollback() does not set isDisposed, so calling Dispose unit of work after Rollback() still runs its full disposal logic rather than being skipped — see that page for the exact flag-order and IsNull()-side-effect caveats.

Note: Statements marked with * were inferred from code structure, naming conventions, or contextual evidence where direct confirmation was not available in source artifacts. Statements marked with ** could not be confirmed; treat as unknown until verified.

Sources

  • src/SAIF.NetCore.Nhibernate/SAIF.NetCore.NHibernate/SAIF.NetCore.NHibernate/UnitOfWork.cs