---
title: SAIF.Framework.Data.NHibernate architecture
summary: Architectural responsibilities, boundaries, and interaction patterns.
status: draft
owner: Framework
last_reviewed: 2026-09-15
library: SAIF.Framework.Data.NHibernate
reference_type: package
---

# SAIF.Framework.Data.NHibernate architecture

`SAIF.Framework.Data.NHibernate` is a layered NHibernate ORM data-access library that implements and consumes — rather than defines — the unit-of-work/repository/session seam used by consuming applications: engine-specific configuration/session factories, session-strategy adapters for stateful and stateless NHibernate sessions, a transaction-scoped unit of work, generic CRUD repositories, and 42 FluentNHibernate entity mappings across 10 domain areas. The seam contracts (`IUnitOfWork`, `IRepository<TEntity>`, `ISessionHelper`, `IDataEntity`) are supplied by the `SAIF.Framework.Core` package dependency, namespace `SAIF.Data` — an ordinary, forward-direction dependency, the same direction as this library's dependency on NHibernate — brought into scope via C# enclosing-namespace lookup (this project's `RootNamespace`, `SAIF.Data.NHibernate`, nests under Core's `SAIF.Data`) rather than an explicit `using SAIF.Data;` directive. This project's own `SAIF.Data.NHibernate.Interfaces` namespace is unrelated and holds only its 3 locally-declared interfaces (`IConfigurationFactory`, `ISessionStrategy`, `IUnitOfWorkFactory`).

## Responsibilities
- Build and cache engine-specific NHibernate `Configuration`/`ISessionFactory` instances (Oracle and SQL Server), keyed by connection string, via `Factories/ConfigurationFactoryBase` and its `OracleConfigurationFactory`/`SqlServerConfigurationFactory` subclasses.
- Construct `NHibernateUnitOfWork` instances that wrap either a stateful (`ISession`) or stateless (`IStatelessSession`) NHibernate session behind a common `ISessionStrategy` seam, via `Factories/UnitOfWorkFactoryBase` and its `OracleUnitOfWorkFactory`/`OracleStatelessUnitOfWorkFactory`/`SqlServerUnitOfWorkFactory` subclasses.
- Own the transaction boundary (`Begin()`/`Commit()`/`Rollback()`) for all repositories obtained from a given unit of work (`UnitOfWork/NHibernateUnitOfWork`).
- Provide a generic CRUD/query surface (`Add`/`Update`/`Delete`/`Query`/`Get`/`Load`/`SystemDate`) over either session strategy (`Repositories/RepositoryBase<TEntity>`, `Repositories/Repository<TEntity>`).
- Supply engine-specific server-time retrieval with a local-clock fallback (`SessionHelper/SessionHelperBase` and its `OracleSessionHelper`/`SqlServerSessionHelper` subclasses).
- Declaratively map 42 entities from the `SAIF.Data.Model.*` namespaces to their relational schema via FluentNHibernate `ClassMap<TEntity>` classes (`Mappings/`), discovered through assembly scanning rather than direct external consumption.
- Provide one domain-specific extension (`Repositories/Imaging/ImageRepositoryExtensions`) that generates Oracle sequence-based IDs for the Imaging domain via a direct SQL escape hatch.

## Component boundaries
- **Interfaces**: `IConfigurationFactory`, `ISessionStrategy`, `IUnitOfWorkFactory` — the three seam contracts declared and owned by this project, in its own `SAIF.Data.NHibernate.Interfaces` namespace, that decouple database-engine-specific code from the repository/unit-of-work API; this is the public API surface consumers use when wiring up dependency injection. In addition to these three locally-declared interfaces, the library's classes implement four seam contracts owned by the upstream `SAIF.Framework.Core` package dependency, namespace `SAIF.Data` (brought into scope via C# enclosing-namespace lookup, not via the `SAIF.Data.NHibernate.Interfaces` namespace above) — `IUnitOfWork` (implemented by `NHibernateUnitOfWork`), `IRepository<TEntity>` (implemented by `RepositoryBase<TEntity>`), `ISessionHelper` (implemented by `SessionHelperBase`), and `IDataEntity` (used as a generic constraint on `GetRepository<TEntity>()`); see [Dependencies](dependencies.md) for the `SAIF.Framework.Core` package-dependency evidence.
- **Factories**: `ConfigurationFactoryBase`/`OracleConfigurationFactory`/`SqlServerConfigurationFactory` build and cache NHibernate `Configuration`/`ISessionFactory` objects (`ConcurrentDictionary<string, PersistenceConfiguration>` and `ConcurrentDictionary<string, ISessionFactory>` keyed by connection string); `UnitOfWorkFactoryBase`/`OracleUnitOfWorkFactory`/`OracleStatelessUnitOfWorkFactory`/`SqlServerUnitOfWorkFactory` build `NHibernateUnitOfWork` instances per engine/session-strategy combination. This is the only layer that touches `Fluently.Configure()`, `OracleDataClientConfiguration`, or `MsSqlConfiguration`.
- **Sessions**: `StatefulSessionStrategy`/`StatelessSessionStrategy` (both internal) implement `ISessionStrategy` over a real NHibernate `ISession`/`IStatelessSession`, translating `Add`/`Update`/`Delete`/`Get`/`Load`/`Query` into the correct call for each session type. These classes encapsulate the stateful-vs-stateless semantics divergence and are never exposed directly outside the assembly.
- **SessionHelper**: `SessionHelperBase`/`OracleSessionHelper`/`SqlServerSessionHelper` (all internal) expose `SystemDate` via an engine-specific SQL string, falling back to `DateTime.Now` on a null/min/max result. This is an infrastructure utility consumed by `RepositoryBase<TEntity>.SystemDate`, not a business rule.
- **UnitOfWork**: `NHibernateUnitOfWork` is the transaction boundary. `Begin()`/`Commit()`/`Rollback()` wrap the NHibernate `ITransaction`; `GetRepository<TEntity>()` hands out a `Repository<TEntity>` bound to this unit of work's session, so all repositories obtained from one instance share its transaction.
- **Repositories**: `RepositoryBase<TEntity>`/`Repository<TEntity>` provide a generic CRUD + LINQ query surface over `ISessionStrategy`; `Repositories/Imaging/ImageRepositoryExtensions` adds three Oracle sequence-based ID-generation extension methods scoped to the Imaging domain — the one custom-repository-style extension found in this library.
- **Mappings**: 42 `ClassMap<TEntity>` classes across 10 domain sub-folders (`Contacts`, `Deployment`, `Employee`, `Imaging`, `Menu`, `PDF`, `Preferences`, `Printing`, `Users`, `WebContext`), mirroring the `SAIF.Data.Model.*` namespace structure. The mapping is the declarative persistence contract — no inline CRUD SQL or business-criteria logic exists for these entities.

## Interaction model
1. A consumer obtains an `IUnitOfWorkFactory` implementation (`OracleUnitOfWorkFactory`, `OracleStatelessUnitOfWorkFactory`, or `SqlServerUnitOfWorkFactory`), constructed with the matching `IConfigurationFactory`.
2. `GetUnitOfWork(connectionString, batchSize, showSql)` resolves (or builds and caches) an `ISessionFactory` for that connection string via `ConfigurationFactory.GetConfiguration(connection, batchSize, showSql)` and `BuildSessionFactory()`.
3. The factory opens an `ISession` (`CreateSessionUnitOfWork`) or `IStatelessSession` (`CreateStatelessSessionUnitOfWork`), wraps it in `StatefulSessionStrategy`/`StatelessSessionStrategy`, builds the matching `ISessionHelper` (`OracleSessionHelper`/`SqlServerSessionHelper`), and returns a `NHibernateUnitOfWork`.
4. The consumer calls `Begin()` to clear the session and start a `ReadCommitted` transaction, calls `GetRepository<TEntity>()` one or more times to obtain `Repository<TEntity>` instances sharing that unit of work's session, performs `Add`/`Update`/`Delete`/`Query`/`Get`/`Load` operations, then calls `Commit()` or `Rollback()`.
5. Repository operations diverge by session strategy: `Update()` is a no-op under the stateful strategy (relying on NHibernate's automatic dirty-checking at commit) but performs an explicit `IStatelessSession.Update(entity)` call under the stateless strategy; `Get`/`Load` return a lazy proxy under the stateful strategy but both delegate to a non-lazy `IStatelessSession.Get<TEntity>(id)` under the stateless strategy.
6. `ImageRepositoryExtensions` bypasses the mapped-entity path entirely for ID generation: it casts `IRepository<T>` to the concrete `Repository<T>` to reach the underlying `Session` and issues a direct `CreateSqlQuery("select {sequence}.nextval from dual")` call — the one direct-SQL escape hatch in this library.

## Architecture diagram
The following diagram shows the high-level component interactions across the layered NHibernate stack.

```mermaid
flowchart TD
	Consumer["Consuming application"] --> IUoWF["IUnitOfWorkFactory<br>(Oracle / OracleStateless / SqlServer)"]
	IUoWF --> ICF["IConfigurationFactory<br>(Oracle / SqlServer)"]
	ICF --> Mappings["Mappings/<br>42 ClassMap&lt;TEntity&gt; (assembly scan)"]
	IUoWF --> NUoW["NHibernateUnitOfWork"]
	NUoW --> ISS["ISessionStrategy<br>(Stateful / Stateless)"]
	NUoW --> ISH["ISessionHelper<br>(Oracle / SqlServer)"]
	NUoW --> Repo["Repository&lt;TEntity&gt;<br>(RepositoryBase&lt;TEntity&gt;)"]
	Repo --> ISS
	Repo --> ImgExt["ImageRepositoryExtensions<br>(Oracle sequence NEXTVAL)"]
	ISS --> NH["NHibernate ISession / IStatelessSession"]
```

## Sequence diagram
The following diagram shows the primary unit-of-work/repository request sequence for a transactional operation.

```mermaid
sequenceDiagram
	participant Consumer
	participant Factory as IUnitOfWorkFactory
	participant UoW as NHibernateUnitOfWork
	participant Repo as "Repository(TEntity)"
	participant Session as ISessionStrategy
	Consumer->>Factory: GetUnitOfWork(connectionString, batchSize, showSql)
	Factory-->>Consumer: NHibernateUnitOfWork
	Consumer->>UoW: Begin()
	UoW->>Session: Clear() / BeginTransaction(ReadCommitted)
	Consumer->>UoW: GetRepository(TEntity)()
	UoW-->>Consumer: Repository(TEntity)
	Consumer->>Repo: Add/Update/Delete/Query/Get/Load
	Repo->>Session: delegate call
	Session-->>Repo: result
	Consumer->>UoW: Commit()
	UoW->>Session: transaction.Commit() / Session.Clear()
```

## Design constraints
- `Begin()` enforces that no transaction is already active (`IsTransactionInProgress()` check) and throws `InvalidOperationException` otherwise; `Commit()` enforces that `Begin()` was called first and throws `InvalidOperationException` otherwise — both are structural invariants rather than domain business rules.
- `Rollback()` disposes the session unconditionally, even if no transaction was active.
- A `Repository<TEntity>` is scoped to exactly one `NHibernateUnitOfWork` instance's session for its entire lifetime; repositories are not reusable across units of work, so all repositories obtained from the same unit-of-work instance participate in the same transaction.
- Deserialization of a previously-serialized `Configuration` (`BinaryFormatter` reading `{assemblyDirectory}\{Oracle|SqlServer}.config`) silently swallows any exception and falls back to building a fresh configuration, rather than surfacing the deserialization failure.
- `SystemDate` falls back to local `DateTime.Now` if the underlying database query returns null, `DateTime.MinValue`, or `DateTime.MaxValue`, so callers relying on it for audit timestamps may silently receive local server time instead of database time.
- The stateless session strategy requires explicit `Update()` calls for every entity change (no automatic dirty-checking/flush-on-commit), unlike the stateful strategy where `Update()` is a no-op relying on NHibernate's automatic dirty-checking.
- Entity POCOs mapped throughout `Mappings/` (`SAIF.Data.Model.*`) and the seam types `IUnitOfWork`, `IRepository<TEntity>`, `ISessionHelper`, `IDataEntity` consumed by this library are supplied by the `SAIF.Framework.Core` package dependency (namespace `SAIF.Data`), not by the consuming application: this project's `RootNamespace` (`SAIF.Data.NHibernate`) nests under Core's `SAIF.Data` namespace via C# enclosing-namespace lookup, so these types are in scope without a `using SAIF.Data;` directive. This is an ordinary upstream package dependency, not a consumer-imposed design constraint. See [Dependencies](dependencies.md) for the `SAIF.Framework.Core` package-dependency evidence.
- This library performs no authorization/authentication enforcement and reads no application-level configuration keys (connection strings are supplied as plain method parameters by the caller); resolving those values from environment/app configuration is the consuming application's responsibility.

> **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.

## Related documentation
- [Index](index.md)
- [Overview](overview.md)
- [Dependencies](dependencies.md)
- [Operations and processes](operations-processes.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`
- `Interfaces/IConfigurationFactory.cs`
- `Interfaces/IUnitOfWorkFactory.cs`
- `Interfaces/ISessionStrategy.cs`
- `Sessions/StatefulSessionStrategy.cs`
- `Sessions/StatelessSessionStrategy.cs`
- `SessionHelper/SessionHelperBase.cs`
- `UnitOfWork/NHibernateUnitOfWork.cs`
- `Repositories/RepositoryBase.cs`
- `Repositories/Repository.cs`
- `Repositories/Imaging/ImageRepositoryExtensions.cs`
- `Mappings/` (42 `ClassMap<TEntity>` files across 10 domain sub-folders)
