What Is a Multi-Tenant Application
A multi-tenant application is a software architecture design that allows a single instance of software to serve multiple customers. Each customer is called a tenant; data across tenants is automatically isolated, so one tenant’s data never affects another’s.
Search for “multi-tenancy” on the ZeroDegree Programming website to watch the multi-tenant design video tutorial.
Isolating Tenant Data with a Shared Table and a Discriminator Column
The shared-table multi-tenant design stores data for multiple tenants in a single table, distinguishing each tenant’s rows with an added TenantId column.
Querying Multi-Tenant Data
public class FieldIsolationServiceDbContext(DbContextOptions<FieldIsolationServiceDbContext> options, ICurrentTenant currentTenant) : DbContext(options)
{
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Product>().Property(p => p.Name).HasMaxLength(32);
foreach (IMutableEntityType entityType in modelBuilder.Model.GetEntityTypes())
{
if (entityType.ClrType.IsAssignableTo(typeof(IMultiTenant)))
{
modelBuilder.Entity(entityType.ClrType).AddQueryFilter<IMultiTenant>(e => e.TenantId == currentTenant.TenantId);
}
}
base.OnModelCreating(modelBuilder);
}
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.AddInterceptors(new TenantSaveChangesInterceptor(currentTenant));
base.OnConfiguring(optionsBuilder);
}
}
Setting the Tenant ID Automatically When Saving Data
private void MultiTenancyTracking(DbContext dbContext)
{
IEnumerable<EntityEntry<IMultiTenant>> multiTenancyEntries = dbContext.ChangeTracker.Entries<IMultiTenant>().Where(entry => entry.State == EntityState.Added || entry.State == EntityState.Modified);
multiTenancyEntries?.ToList().ForEach(entityEntry =>
{
entityEntry.Entity.TenantId ??= currentTenant.TenantId;
});
}
Isolating Tenant Data with Multiple Databases
The multi-database multi-tenant design creates a separate database for each tenant, distinguishing tenant data by the database connection string.
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
if (currentTenant.TenantId != null)
{
string? connectionString = configuration.GetConnectionString(currentTenant.TenantId);
optionsBuilder.UseNpgsql(connectionString);
}
base.OnConfiguring(optionsBuilder);
}
ZeroFramework TenantDbConnectionInterceptor
Schema-Based Multi-Tenant Design
A schema-based multi-tenant design creates a separate schema for each tenant, distinguishing tenant data by schema.
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
if (currentTenant.TenantId != null)
{
modelBuilder.HasDefaultSchema(currentTenant.TenantId);
}
base.OnModelCreating(modelBuilder);
}
Currently EF Core does not create schemas automatically during migrations, so schemas must be created manually.
Implementing Multi-Tenancy in ZeroFramework
Design a tenants table, include the tenant ID in the login token, extract the tenant ID from the token on every request, look up the tenant information by tenant ID, store the tenant information in the current request’s context, and use it to isolate tenant data.