Skip to content

SAIF.NetCore.NHibernate architecture

SAIF.NetCore.NHibernate is the repository's NHibernate data-access infrastructure provider: it defines a IUnitOfWork/IUnitOfWorkFactory/IRepository<TEntity>/ISessionStrategy stack, a named-options configuration model, and per-engine driver configurations for Oracle, SQL Server, and PostgreSQL, through FluentNHibernate — but it ships no entity mappings or business schema of its own, leaving that to consuming applications.

Responsibilities

  • Define the unit-of-work and repository abstractions (IUnitOfWork, IUnitOfWorkFactory, IRepository<TEntity>, ISessionStrategy, ISessionHelper) that consuming applications build entity access on top of.
  • Build and cache an NHibernate ISessionFactory per connection string (UnitOfWorkFactory), selecting the Oracle, SQL Server, or PostgreSQL driver configuration from UnitOfWorkOptions.Database.
  • Provide stateful and stateless session strategy implementations (StatefulSessionStrategy, StatelessSessionStrategy) with matching engine-specific SessionHelper implementations for a trivial system-date query.
  • Supply a named-options configuration pattern (UnitOfWorkOptions, ConfigureUnitOfWorkOptions) through which a consuming application reads connection strings and environment settings via IEnvironmentClient/IDBConnectionClient.
  • Register the modern DI surface (AddNhibernateServices) while retaining 13 legacy registration methods as [Obsolete("Due to functional changes between .net core 2.2 and .net core 3.1, this method is obsolete to avoid startup errors.", true)] compile-error stubs that force migration off the pre-.NET Core 3.1 API.

Component boundaries

  • UnitOfWorkFactory / UnitOfWork: lifecycle — builds/caches the ISessionFactory per connection string, begins/commits/rolls back the unit of work, and hands out Repository<TEntity> instances. UnitOfWork's constructor is internal, so only UnitOfWorkFactory can create one.
  • Repository<TEntity> (implements IRepository<TEntity>): the CRUD/query facade over a concrete UnitOfWork, exposing Add/Update/Delete/Get/Load/Query().
  • ISessionStrategy implementations (StatefulSessionStrategy, StatelessSessionStrategy): wrap NHibernate's ISession/IStatelessSession, the only point where consumers can reach the raw NHibernate session.
  • ISessionHelper implementations (OracleSessionHelper, SqlServerSessionHelper, PostgreSqlSessionHelper): engine-specific system-date queries used by SessionHelperBase.SystemDate.
  • BaseConfiguration and its engine subclasses (OracleConfiguration, SqlConfiguration, PostGreSqlConfiguration): FluentNHibernate driver/dialect configuration per DatabaseType, plus optional SQL debug output via SqlDebugOutputInterceptor.
  • UnitOfWorkOptions / ConfigureUnitOfWorkOptions (abstract): the named-options POCO and the pattern a consuming application subclasses to resolve its connection string and engine from IEnvironmentClient/IDBConnectionClient.
  • IServicesCollectionExtensions (AddNhibernateServices): the current DI registration surface. IServicesNhibernateExtensions: 13 legacy registration methods, each marked [Obsolete] with its error flag set to true, so calling any of them is a compile-time error — they are retained in the public API surface solely as compile-time-error migration diagnostics, not as working source-compatible registration methods, and must not be called.
  • Contract boundary: IEnvironmentClient and IDBConnectionClient are declared in SAIF.NetCore.ServiceContracts; this library only consumes them as constructor-injected fields on a consumer-authored ConfigureUnitOfWorkOptions subclass.

Interaction model

  • A consuming application calls AddNhibernateServices(assemblies, debugOutputSql: false) (the non-generic overload requires debugOutputSql) or the generic AddNhibernateServices<T>() to register IUnitOfWorkFactory as a singleton and UnitOfWorkAccessor as a scoped service — nothing else.
  • As a separate host registration, the consuming application must also register its own ConfigureUnitOfWorkOptions subclass, typically via services.ConfigureOptions<TConfigurer>(); AddNhibernateServices does not register this configurer or any IConfigureOptions<UnitOfWorkOptions> on the consumer's behalf.
  • That subclass is constructed with IEnvironmentClient and IDBConnectionClient (both from SAIF.NetCore.ServiceContracts) and implements GetConnectionString() by calling the appropriate IDBConnectionClient method (GetOracleConnection/GetSqlServerConnection/GetPostgreSqlServerConnection) for its configured DatabaseType.
  • At request time, the application resolves IUnitOfWorkFactory (directly, or through UnitOfWorkAccessor.UnitOfWork(key)) and calls GetUnitOfWork(options); UnitOfWorkFactory builds (or reuses a cached) ISessionFactory keyed by connection string, selects the stateful or stateless session strategy, and returns an IUnitOfWork.
  • The application then calls GetRepository<TEntity>() on the unit of work to obtain a Repository<TEntity> bound to its own ClassMap<TEntity> entity mappings — which this library does not supply — and issues Add/Update/Delete/Get/Load/Query() calls against it.
  • An unrecognized DatabaseType value silently falls back to Oracle for both the driver configuration and the session helper*, rather than failing fast.
  • No SAIF library in this repository consumes this package: SAIF.NetCore.ServiceExtensions does not reference it, even though the top-level architecture diagram draws a composition arrow from ServiceExtensions to the data group — applications must reference SAIF.NetCore.NHibernate directly*.

Architecture diagram

The following diagram shows how a consuming application's unit-of-work options drive session-factory construction and entity access.

flowchart TD
    A["Consuming app:<br>ConfigureUnitOfWorkOptions subclass"] --> B["IEnvironmentClient /<br>IDBConnectionClient"]
    B --> C["UnitOfWorkOptions:<br>Database, ConnectionString"]
    C --> D[UnitOfWorkFactory.GetUnitOfWork]
    D --> E{"ISessionFactory cached<br>for connection string?"}
    E -->|no| F["BaseConfiguration subclass:<br>Oracle / Sql / PostGreSql driver"]
    F --> G["FluentNHibernate:<br>BuildSessionFactory"]
    E -->|yes| H[Reuse cached ISessionFactory]
    G --> I["SessionStrategy:<br>Stateful or Stateless"]
    H --> I
    I --> J["UnitOfWork"]
    J --> K["Repository&lt;TEntity&gt;:<br>consumer-supplied ClassMap"]

Sequence diagram

The following diagram shows the primary request/response sequence for obtaining a unit of work and querying an entity.

sequenceDiagram
    participant App as "Consuming application"
    participant Factory as UnitOfWorkFactory
    participant Config as "ConfigureUnitOfWorkOptions"
    participant NH as "NHibernate / FluentNHibernate"
    participant Repo as "Repository#lt;TEntity#gt;"
    App->>Config: GetConnectionString() via IDBConnectionClient
    App->>Factory: GetUnitOfWork(options)
    Factory->>NH: BuildSessionFactory (cached by connection string)
    NH-->>Factory: ISessionFactory
    Factory-->>App: IUnitOfWork
    App->>Repo: unitOfWork.GetRepository<TEntity>()
    App->>Repo: Query() / Get(id) / Add(entity)
    Repo->>NH: Execute against ISession/IStatelessSession
    NH-->>Repo: Result
    Repo-->>App: Result

Design constraints

  • The library ships no entity mappings or business schema; the only concrete SQL it owns is three engine-specific CreateSqlQuery system-date statements and one generic LINQ call site (UnitOfWork.TestConnection<TEntity>()).
  • ISessionStrategy exposes the raw NHibernate ISession/IStatelessSession, so consumers can bypass the repository abstraction entirely; it also declares Dispose() without implementing IDisposable.
  • 13 legacy registration methods in IServicesNhibernateExtensions are [Obsolete("Due to functional changes between .net core 2.2 and .net core 3.1, this method is obsolete to avoid startup errors.", true)] no-ops; the checked-in README.md still documents this stale API and should not be treated as current.
  • The static SessionFactories cache is keyed only by connection string, so two ConfigureUnitOfWorkOptions with the same connection string but different mapping assemblies would collide on cache lookup — unconfirmed in practice**.
  • NHibernate's own resolved version is not pinned in this repository; it arrives transitively through FluentNHibernate 3.0.1**.

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/SAIF.NetCore.NHibernate.csproj
  • src/SAIF.NetCore.Nhibernate/SAIF.NetCore.NHibernate/SAIF.NetCore.NHibernate/Factories/UnitOfWorkFactory.cs
  • src/SAIF.NetCore.Nhibernate/SAIF.NetCore.NHibernate/SAIF.NetCore.NHibernate/Repository.cs
  • src/SAIF.NetCore.Nhibernate/SAIF.NetCore.NHibernate/SAIF.NetCore.NHibernate/UnitOfWork.cs
  • src/SAIF.NetCore.Nhibernate/SAIF.NetCore.NHibernate/SAIF.NetCore.NHibernate/Options/ConfigureUnitOfWorkOptions.cs
  • src/SAIF.NetCore.Nhibernate/SAIF.NetCore.NHibernate/SAIF.NetCore.NHibernate/Extensions/IServicesCollectionExtensions.cs
  • Architecture.md