---
title: SAIF.Framework.Data.NHibernate operations and processes
summary: Business and technical operations supported by this library.
status: draft
owner: Framework
last_reviewed: 2026-09-15
library: SAIF.Framework.Data.NHibernate
reference_type: package
---

# SAIF.Framework.Data.NHibernate operations and processes

This page catalogs the operations this library performs and, for each, the step-by-step logic that implements it.

## Operation catalog

| Operation | Intent | Trigger | Inputs | Outputs | Rules | Tables | SQL |
| --- | --- | --- | --- | --- | --- | --- | --- |
| [Build and cache an NHibernate configuration for a connection string](#build-and-cache-an-nhibernate-configuration-for-a-connection-string) | Build (or reuse a cached) NHibernate `Configuration`/`ISessionFactory` for a given connection string. | `IConfigurationFactory.GetConfiguration(connection, batchSize, showSql)` | Connection string, batch size, show-SQL flag | Cached `PersistenceConfiguration` | — | — | — |
| [Obtain a unit of work for a connection string](#obtain-a-unit-of-work-for-a-connection-string) | Build (or reuse a cached) `ISessionFactory` and hand back a `NHibernateUnitOfWork` bound to a fresh session. | `IUnitOfWorkFactory.GetUnitOfWork(connectionString, batchSize, showSql)` | Connection string, batch size, show-SQL flag | `NHibernateUnitOfWork` bound to a freshly opened `ISession`/`IStatelessSession` | — | — | — |
| [Unit of work transaction lifecycle](#unit-of-work-transaction-lifecycle) | Manage the begin/commit/rollback lifecycle of a unit of work's transaction. | Consumer calls `Begin()`/`Commit()`/`Rollback()`/`Dispose()` on a `NHibernateUnitOfWork` | — | Transaction committed or rolled back; session cleared or disposed | [BR-001](business-rules.md#br-001-transaction-already-in-progress), [BR-002](business-rules.md#br-002-commit-requires-a-prior-begin) | — | — |
| [Repository CRUD and query operations (stateful vs stateless divergence)](#repository-crud-and-query-operations-stateful-vs-stateless-divergence) | Perform generic persistence operations against a mapped entity, diverging by session strategy. | Consumer calls `Add`/`Update`/`Delete`/`Query`/`Get`/`Load`/`SystemDate` on a `Repository<TEntity>` | Entity instance, entity ID, or none (for `Query`/`SystemDate`) | Entity persisted/retrieved; behavior differs by session strategy | [BR-003](business-rules.md#br-003-stateless-updates-require-explicit-persistence), [BR-004](business-rules.md#br-004-systemdate-falls-back-to-local-clock) | — | [SQL-004](sql/SQL-004-saif-framework-data-nhibernate.md) |
| [Oracle sequence based ID generation (Imaging domain)](#oracle-sequence-based-id-generation-imaging-domain) | Obtain the next value of an Oracle sequence directly, bypassing the mapped-entity ID-generation path. | Consumer calls `GetNextImageId`/`GetNextTowId`/`GetNextImageAffiliationId` extension methods | `IRepository<Image>` / `IRepository<ImageSystem>` / `IRepository<ImageAffiliation>` instance | `long` — next sequence value | — | [image](database-tables.md#image), [IMGSYS](database-tables.md#imgsys), [IMAGE_AFFILIATION](database-tables.md#image_affiliation) | [SQL-001](sql/SQL-001-saif-framework-data-nhibernate.md), [SQL-002](sql/SQL-002-saif-framework-data-nhibernate.md), [SQL-003](sql/SQL-003-saif-framework-data-nhibernate.md) |

## Process flow diagram

The following diagram visualizes the primary operational flow from configuration through transaction commit.

```mermaid
flowchart TD
	Start([Consumer requests unit of work]) --> Cfg[Build/cache NHibernate Configuration]
	Cfg --> UoW["Build/cache ISessionFactory; open Session/StatelessSession"]
	UoW --> Begin[Begin transaction]
	Begin --> Ops[Repository Add/Update/Delete/Query/Get/Load]
	Ops --> Decision{Success?}
	Decision -->|Yes| Commit([Commit transaction])
	Decision -->|No| Rollback([Rollback transaction])
```

## Build and cache an NHibernate configuration for a connection string

A consumer requests an NHibernate `Configuration` for a connection string through `IConfigurationFactory.GetConfiguration(connection, batchSize, showSql)`, typically invoked from `UnitOfWorkFactoryBase.BuildSessionFactory`.

| Field | Value |
| --- | --- |
| **Trigger** | `IConfigurationFactory.GetConfiguration(connection, batchSize, showSql)` call |
| **Inputs** | Connection string, batch size, show-SQL flag |
| **Outputs** | A cached `PersistenceConfiguration` (wraps NHibernate `Configuration` + a serialized-config file path) |
| **Primary components** | `Factories/ConfigurationFactoryBase.cs`, `Factories/OracleConfigurationFactory.cs`, `Factories/SqlServerConfigurationFactory.cs` |

### Steps — Build and cache an NHibernate configuration for a connection string
1. Look up `connection` in the static `ConcurrentDictionary<string, PersistenceConfiguration> Configurations`; if present, return it (cache hit).
2. On cache miss, call `CreateConfiguration(connection, batchSize, showSql)`.
3. Attempt to deserialize a previously-serialized `Configuration` from a binary file at `{assemblyDirectory}\{GetConfigurationFileName()}` (`Oracle.config` or `SqlServer.config`) using `BinaryFormatter`.
   - On any exception (including file-not-found): fall back to `null`.
4. If no serialized configuration was found, build a new one via the engine-specific `GetNewConfiguration(formatSql)` override, which configures `Fluently.Configure()` with the engine driver and scans this assembly (and any `ClassMapAssemblyList` assemblies) for `ClassMap<T>`/`.hbm.xml` mappings.
5. Set `Environment.ConnectionString`, `Environment.BatchSize`, `Environment.ShowSql` on the configuration.
6. Return a `PersistenceConfiguration` wrapping the built `Configuration` and the serialized-config file path.

### Failure and edge-case behavior — Build and cache an NHibernate configuration for a connection string
- Deserialization failures are silently swallowed (caught `Exception`, configuration set to `null`, forcing a fresh build).

## Obtain a unit of work for a connection string

A consumer requests a `NHibernateUnitOfWork` bound to a freshly opened session through `IUnitOfWorkFactory.GetUnitOfWork(connectionString, batchSize, showSql)`.

| Field | Value |
| --- | --- |
| **Trigger** | `IUnitOfWorkFactory.GetUnitOfWork(connectionString, batchSize, showSql)` |
| **Inputs** | Connection string, batch size, show-SQL flag |
| **Outputs** | A `NHibernateUnitOfWork` bound to a freshly opened `ISession`/`IStatelessSession` |
| **Primary components** | `Factories/UnitOfWorkFactoryBase.cs`, `Factories/OracleUnitOfWorkFactory.cs`, `Factories/OracleStatelessUnitOfWorkFactory.cs`, `Factories/SqlServerUnitOfWorkFactory.cs` |

### Steps — Obtain a unit of work for a connection string
1. Lock the static `SessionFactories` dictionary (per-process synchronization).
2. Get-or-add an `ISessionFactory` for `connectionString` via `BuildSessionFactory`, which resolves the `PersistenceConfiguration` (see [Build and cache an NHibernate configuration for a connection string](#build-and-cache-an-nhibernate-configuration-for-a-connection-string)) and calls `Configuration.BuildSessionFactory()`.
3. Call the engine-specific `CreateUnitOfWork(factory)` override:
   - `OracleUnitOfWorkFactory`/`SqlServerUnitOfWorkFactory` → `CreateSessionUnitOfWork` (stateful `ISession` via `factory.OpenSession()`, wrapped in `StatefulSessionStrategy`).
   - `OracleStatelessUnitOfWorkFactory` → `CreateStatelessSessionUnitOfWork` (`IStatelessSession` via `factory.OpenStatelessSession()`, wrapped in `StatelessSessionStrategy`).
4. Build the matching `ISessionHelper` (`OracleSessionHelper`/`SqlServerSessionHelper`) via `BuildSessionHelper`.
5. Return a new `NHibernateUnitOfWork(sessionStrategy, sessionHelper)`.

## Unit of work transaction lifecycle

Once a consumer has obtained a `NHibernateUnitOfWork`, it manages the transaction boundary explicitly through `Begin()`, `Commit()`, and `Rollback()`.

| Field | Value |
| --- | --- |
| **Trigger** | Consumer calls `Begin()` after obtaining a `NHibernateUnitOfWork` |
| **Outputs** | Transaction committed or rolled back; session cleared |
| **Primary components** | `UnitOfWork/NHibernateUnitOfWork.cs` |

### Steps — Unit of work transaction lifecycle
1. `Begin()`: check `IsTransactionInProgress()` (transaction `IsActive`, `WasCommitted`, or `WasRolledBack`).
   - If true: throw `InvalidOperationException("There is a transaction already in progress.")` ([BR-001](business-rules.md#br-001-transaction-already-in-progress)).
   - Otherwise: call `Session.Clear()` then `Session.BeginTransaction(IsolationLevel.ReadCommitted)`.
2. Repository operations (`Add`/`Update`/`Delete`/`Query`/`Get`/`Load`) are performed via repositories obtained from `GetRepository<TEntity>()`, all sharing this unit of work's `Session` ([BR-005](business-rules.md#br-005-repository-is-scoped-to-one-unit-of-work)).
3. `Commit()`: get the current `ITransaction`.
   - If `!transaction.IsActive`: throw `InvalidOperationException("You must call Begin() on the unit of work before you can commit.")` ([BR-002](business-rules.md#br-002-commit-requires-a-prior-begin)).
   - Otherwise: call `transaction.Commit()` then `Session.Clear()`.
4. `Rollback()`: if the transaction `IsActive`, call `transaction.Rollback()`; then always call `Session.Dispose()`.
5. `Dispose()`: standard dispose pattern; if not already disposed and `Session` is not null, call `Session.Dispose()`.

### Failure and edge-case behavior — Unit of work transaction lifecycle
- `Begin()` and `Commit()` both enforce state invariants via `InvalidOperationException` ([BR-001](business-rules.md#br-001-transaction-already-in-progress), [BR-002](business-rules.md#br-002-commit-requires-a-prior-begin)).
- `Rollback()` disposes the session unconditionally, even if no transaction was active.

## Repository CRUD and query operations stateful vs stateless divergence

A consumer performs generic persistence operations against a mapped entity through a `Repository<TEntity>`; the underlying behavior diverges depending on whether the unit of work uses a stateful or stateless session strategy.

| Field | Value |
| --- | --- |
| **Trigger** | Consumer calls `Add`/`Update`/`Delete`/`Query`/`Get`/`Load`/`SystemDate` on a `Repository<TEntity>` |
| **Outputs** | Entity persisted/retrieved; behavior differs by session strategy |
| **Primary components** | `Repositories/RepositoryBase.cs`, `Sessions/StatefulSessionStrategy.cs`, `Sessions/StatelessSessionStrategy.cs`, `SessionHelper/SessionHelperBase.cs` |

### Steps — Repository CRUD and query operations
1. `RepositoryBase<TEntity>.Add(entity)` → `Session.Add(entity)`.
   - Stateful: `ISession.SaveOrUpdate(entity)` (`Sessions/StatefulSessionStrategy.cs`).
   - Stateless: `IStatelessSession.Insert(entity)` (`Sessions/StatelessSessionStrategy.cs`).
2. `RepositoryBase<TEntity>.Update(entity)` ([BR-003](business-rules.md#br-003-stateless-updates-require-explicit-persistence)):
   - Stateful: no-op (empty method body in `StatefulSessionStrategy.Update`, since dirty-checking auto-flushes on commit).
   - Stateless: explicit `IStatelessSession.Update(entity)` call.
3. `RepositoryBase<TEntity>.Delete(entity)` → `Session.Delete(entity)` in both strategies.
4. `RepositoryBase<TEntity>.Query()` → `Session.Query<TEntity>()` (LINQ to NHibernate) in both strategies.
5. `RepositoryBase<TEntity>.Get(id)` / `Load(id)`:
   - Stateful: `ISession.Get<TEntity>(id)` / `ISession.Load<TEntity>(id)` (lazy proxy).
   - Stateless: both `Get` and `Load` delegate to `IStatelessSession.Get<TEntity>(id)` (no lazy-proxy support in stateless mode).
6. `RepositoryBase<TEntity>.SystemDate` → `SessionHelper.SystemDate` ([BR-004](business-rules.md#br-004-systemdate-falls-back-to-local-clock), [SQL-004](sql/SQL-004-saif-framework-data-nhibernate.md)) → runs the engine-specific date SQL and falls back to `DateTime.Now` if the result is null/min/max.

### Failure and edge-case behavior — Repository CRUD and query operations
- No exceptions are raised by the CRUD/query steps themselves; the only fallback behavior is `SystemDate`'s local-clock substitution ([BR-004](business-rules.md#br-004-systemdate-falls-back-to-local-clock)).

## Oracle sequence based ID generation Imaging domain

A consumer calls one of three extension methods to obtain the next value of an Oracle sequence directly, bypassing the mapped-entity ID-generation path.

| Field | Value |
| --- | --- |
| **Trigger** | Consumer calls `GetNextImageId`, `GetNextTowId`, or `GetNextImageAffiliationId` extension methods on `IRepository<Image>`/`IRepository<ImageSystem>`/`IRepository<ImageAffiliation>` |
| **Outputs** | `long` — next sequence value |
| **Primary components** | `Repositories/Imaging/ImageRepositoryExtensions.cs` |

### Steps — Oracle sequence based ID generation, Imaging domain
1. Cast the `IRepository<T>` to the concrete `Repository<T>` to access its internal `Session`.
2. Run `Session.CreateSqlQuery("select {sequence}.nextval from dual")` — direct SQL escape hatch (not entity-mapped), one of three:
   - `image_seq` ([SQL-001](sql/SQL-001-saif-framework-data-nhibernate.md), [image](database-tables.md#image))
   - `tower_seq` ([SQL-002](sql/SQL-002-saif-framework-data-nhibernate.md), [IMGSYS](database-tables.md#imgsys))
   - `image_affiliation_seq` ([SQL-003](sql/SQL-003-saif-framework-data-nhibernate.md), [IMAGE_AFFILIATION](database-tables.md#image_affiliation))
3. `Convert.ToInt64(query.UniqueResult())` and return.

### Failure and edge-case behavior — Oracle sequence based ID generation, Imaging domain
- This is the one "direct SQL escape hatch" case in this library — session-level `CreateSqlQuery`, Oracle-only (uses `NEXTVAL FROM DUAL` syntax).
- `ImageAffiliation.Id` also has a native NHibernate sequence generator (`GeneratedBy.Native("IMAGE_AFFILIATION_SEQ")`), making [SQL-003](sql/SQL-003-saif-framework-data-nhibernate.md) a parallel/redundant ID-acquisition path to the mapping's own generator.*

## Database interactions

The following table summarizes which operations read from or write to the database.

| Operation | SQL | Tables | Type |
| --- | --- | --- | --- |
| [Repository CRUD and query operations (stateful vs stateless divergence)](#repository-crud-and-query-operations-stateful-vs-stateless-divergence) | [SQL-004](sql/SQL-004-saif-framework-data-nhibernate.md) | — (`DUAL` pseudo-table / no table) | Read |
| [Oracle sequence based ID generation (Imaging domain)](#oracle-sequence-based-id-generation-imaging-domain) | [SQL-001](sql/SQL-001-saif-framework-data-nhibernate.md), [SQL-002](sql/SQL-002-saif-framework-data-nhibernate.md), [SQL-003](sql/SQL-003-saif-framework-data-nhibernate.md) | [image](database-tables.md#image), [IMGSYS](database-tables.md#imgsys), [IMAGE_AFFILIATION](database-tables.md#image_affiliation) | Read |

## Cross-cutting failure behavior
- Deserialization of a previously-serialized `Configuration` silently swallows any exception and falls back to a fresh build, rather than surfacing the failure.
- `Begin()`/`Commit()` enforce state invariants via `InvalidOperationException` ([BR-001](business-rules.md#br-001-transaction-already-in-progress), [BR-002](business-rules.md#br-002-commit-requires-a-prior-begin)); `Rollback()` disposes the session unconditionally.
- `SystemDate` silently substitutes local `DateTime.Now` when the database query returns null/min/max ([BR-004](business-rules.md#br-004-systemdate-falls-back-to-local-clock)).

> **Note:** Statements marked with `*` were inferred from code structure, naming
> conventions, or contextual evidence where direct confirmation was not available
> in source artifacts.

## Related documentation
- [Index](index.md)
- [Overview](overview.md)
- [Architecture](architecture.md)
- [Dependencies](dependencies.md)
- [Business rules](business-rules.md)
- [Database tables](database-tables.md)
- [SQL catalog](sql/index.md)
- [API reference](api/index.md)

## Sources
- `Factories/ConfigurationFactoryBase.cs`
- `Factories/OracleConfigurationFactory.cs`
- `Factories/SqlServerConfigurationFactory.cs`
- `Factories/UnitOfWorkFactoryBase.cs`
- `Factories/OracleUnitOfWorkFactory.cs`
- `Factories/OracleStatelessUnitOfWorkFactory.cs`
- `Factories/SqlServerUnitOfWorkFactory.cs`
- `UnitOfWork/NHibernateUnitOfWork.cs`
- `Repositories/RepositoryBase.cs`
- `Sessions/StatefulSessionStrategy.cs`
- `Sessions/StatelessSessionStrategy.cs`
- `SessionHelper/SessionHelperBase.cs`
- `Repositories/Imaging/ImageRepositoryExtensions.cs`
