Implementing a MultiTenancy Solution with NHibernate

Cristiano Rodrigues
4 min readJul 5, 2024

--

MultiTenancy, or multitenant, is a fundamental concept in modern software architectures, allowing a single instance of software to serve multiple clients (tenants) in a personalized and secure manner. Implementing a MultiTenancy solution with NHibernate can be challenging. I’ve encountered various implementations, including using multiple `SessionFactories` stored in a `ConcurrentDictionary`. This approach can be particularly complicated if memory usage is a constraint for your project. However, with the right approach, it is possible to develop a robust and scalable solution.

In this article, we will detail the steps and considerations for implementing MultiTenancy using NHibernate, utilizing the “Separate Database” concept to maximize data isolation and security.

1. What is MultiTenancy?

MultiTenancy refers to a software architecture where a single application is used by multiple clients, known as tenants. Each tenant can have their own configurations, data, and customizations in the application. There are three main approaches to MultiTenancy:

- Shared Database, Shared Schema: All tenants share the same database and the same schema, differentiated only by identifying data.
- Shared Database, Separate Schema: All tenants share the same database, but each tenant has their own schema.
- Separate Database: Each tenant has their own independent database.

2. Choosing the Approach

The choice of approach depends on the specific needs of the application, such as scalability, maintenance, security, and data isolation. In this article, we will focus on the “Separate Database” approach, which offers great isolation as each tenant has an independent database, ensuring maximum data security and privacy.

3. Configuring NHibernate for MultiTenancy

NHibernate supports specific configuration for MultiTenancy. During the `DatabaseIntegration` configuration, you specify the type of MultiTenancy (Database or Schema) desired and the provider to be used for MultiTenancy.

Configuration Example

To support MultiTenancy using separate databases in NHibernate, you need to provide a valid and accessible connection string, as NHibernate needs to access the database to generate the `SessionFactory`, and configure the provider for MultiTenancy:


Configuration cfg = new Configuration()
.DataBaseIntegration(db =>
{
db.MultiTenancy = MultiTenancyStrategy.Database;
db.MultiTenancyConnectionProvider<MultiTenancyConnectionProvider>();
db.Dialect<PostgreSQL83Dialect>();
db.ConnectionString = “User ID=postgres;Password=<password>;Host=localhost;Port=5432;Database=postgres;Pooling=true;”;
db.Driver<NpgsqlDriver>();
db.LogSqlInConsole = true;
});

Implementing the MultiTenancyConnectionProvider

The next step is to implement the `MultiTenancyConnectionProvider`, which will allow configuring the specific connection string for each tenant.

public class MultiTenancyConnectionProvider : AbstractMultiTenancyConnectionProvider
{
public MultiTenancyConnectionProvider()
{
}
protected override string GetTenantConnectionString(TenantConfiguration tenantConfiguration, ISessionFactoryImplementor sessionFactory)
{
var tenant = (AppSettingsTenantConfiguration)tenantConfiguration;
return tenant.ConnectionString;
}
}

Integration with Dependency Injection

To facilitate obtaining the connection strings from configurations, you should create a class that inherits from `TenantConfiguration` and configure dependency injection to load settings from `appSettings.json`.


public class AppSettingsTenantConfiguration : TenantConfiguration
{
public string ConnectionString { get; }
public AppSettingsTenantConfiguration(string tenantIdentifier, IConfiguration configuration)
: base(tenantIdentifier)
{
ConnectionString = configuration.GetConnectionString(tenantIdentifier);
}
}

Example of `appSettings.json`

The `appSettings.json` file should contain the tenants’ configurations, as in the example below:

{
"ConnectionStrings": {
"Tenant1": "Server=localhost;Database=tenant01;User Id=postgres;Password=<password>;",
"Tenant2": "Server=localhost;Database=tenant02;User Id=postgres;Password=<password>;"
}
}

Obtaining the `ISession`

After configuring NHibernate and the `MultiTenancyConnectionProvider`, it is necessary to configure obtaining the specific session (`ISession`) for each tenant. This can be done using dependency injection and `IHttpContextAccessor` to capture the tenant identifier from the HTTP request headers.

Registering the `ISession`

Configuring dependency injection to provide the correct session (`ISession`) based on the current tenant:

builder.Services.AddScoped<ISession>(sp =>
{
var context = sp.GetRequiredService<IHttpContextAccessor>();
var tenant = context.HttpContext?.Request.Headers[“x-customer”].ToString();
if (string.IsNullOrEmpty(tenant))
{
throw new Exception("Tenant not specified in the request headers.");
}
var sessionFactory = sp.GetRequiredService<ISessionFactory>();
var tenantConfig = new AppSettingsTenantConfiguration(tenant, sp.GetRequiredService<IConfiguration>());
var session = sessionFactory.WithOptions()
.Tenant(tenantConfig)
.OpenSession();
return session;
});

In the example above, the `ISession` is dynamically configured based on the tenant identifier provided in the HTTP header `x-customer`. The `AppSettingsTenantConfiguration` is used to obtain the specific connection string for the tenant, allowing the session to connect to the correct database.

Executing

Database Tenant01

GET Tenant01

Database Tenant02

GET Tenant02

Conclusion

Implementing MultiTenancy with NHibernate is a process that combines simplicity, robustness, and scalability. Using the `TenantConfiguration` class, we can access/obtain/build the connection strings using Dependency Injection. By following the implementation model and examples provided, you will use MultiTenancy in your NHibernate application, ensuring an efficient and adaptable solution to meet different tenants’ needs.

--

--

Cristiano Rodrigues
Cristiano Rodrigues

Written by Cristiano Rodrigues

Microsoft MVP | Solutions Architect with more than 15 years in software development. In love for Docker and Kubernetes, a specialist in .NET and SQL Server.

No responses yet