Skip to content

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

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.

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

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)