NHibernate, the famous port of Hibernate from the Java world provides an alternative Object Relational Mapping (ORM) for Entity Framework. In this article, we will look at how to configure NHibernate for a multitenant system based on Database per tenant model

Database per Tenant model segregates tenant data in separate databases as opposed to storing them in a single database where each row contains a Tenant Identifier. NHibernate Sessions are designed to connect with a single database and don’t provide built-in support for multi-tenancy. We can work around this by overriding the default DriverConnectionProvider.

public static class NHibernateHelper
{
    private static ISessionFactory _sessionFactory;
    private static string _connectionString;

    private static ISessionFactory SessionFactory
    {
        get
        {
            if (_sessionFactory == null)
            {
                _sessionFactory = Fluently.Configure()
                    .Database(PostgreSQLConfiguration.Standard
                    .ConnectionString(_connectionString)
                    .Provider<MultiTenantConnectionProvider>())
                    .BuildSessionFactory();
            }

            return _sessionFactory;
        }
    }

    public static ISession OpenSession(string connectionString)
    {
        _connectionString = connectionString;
        return SessionFactory.OpenSession();
    }

    private class MultiTenantConnectionProvider 
        : DriverConnectionProvider
    {
        protected override string ConnectionString
        {
            get
            {
                //Replace with your tenant identifier logic
                return _connectionString;
            }
        }
    }
}

The NHibernateHelper class here provides the interface for the higher layer to open a session based on the provided connection string. A new session is created from the current connection string requested.
Notes: This connection string identifier logic is not production-ready and is simply used for demonstration purposes. It still works though.

The next step is our tenant identifier logic which returns the corresponding connection string for the tenant whose request is in flight. The Tenant Identifier is fetched from the User Claims attached with the HttpContext object of ASP.NET Core. Once the Tenant Identifier is retrieved the appropriate connection string is mapped from the IConfiguration interface.

public class TenantService: ITenantService
{
    private readonly IHttpContextAccessor _httpContext;
    private readonly IConfiguration _configuration;

    public string ConnectionString
    {
        get
        {
            var local = _configuration
                .GetConnectionString("LocalDatabase");

            if (!string.IsNullOrWhiteSpace(local))
            {
                return local;
            }

            var claim = _httpContext.HttpContext
                .User.Claims
                .FirstOrDefault(x => x.Type == "appTenantId");
            var tenant = claim?.Value;
            return _configuration[tenant];
        }
    }

    public TenantService(
        IHttpContextAccessor httpContext, 
        IConfiguration configuration)
    {
        _httpContext = httpContext;
        _configuration = configuration;
    }
}

The connection strings are not stored in the appsettings.json file and instead makes use of Azure Key Vault for mapping Tenant Identifier with the connection string with credentials included. The option to include a LocalDatabase provides which will bypass the Tenant logics to help during development.

Database Vectors by Vecteezy

0 Shares:
Leave a Reply

Your email address will not be published. Required fields are marked *

You May Also Like