The package NBB.Data.EntityFramework.MultiTenancy helps implementing a multi-tenant data access solution using Entity Framework.
dotnet add package NBB.Data.EntityFramework.MultiTenancy
When it comes to multi-tenant data access, there are 3 strategies:
-
Database per tenant: in this case, the only thing you have to do, is to provide a connection string per tenant, when registering a DbContext. This strategy offers the biggest isolation level, but is the most expensive one
-
Shared database: when using this strategy, multi-tenant tables are partitioned by a TenantId column. This package provides extensions for EF objects, in order to configure both a shadow state property
TenantId
and a global query filter with the value set toITenantContext.GetTenantId
, so that user queries can remain tenancy agnostic. This strategy offers a poor isolation level so it must be used with caution! -
A mix of shared and dedicated databases: some tenants may be shared in the same database while others have dedicated database instances
This package supports all 3 strategies stated above, so that you can write tenancy ignorant queries
- to configure a multi-tenant entity, you need to call
IsMultiTenant()
inside entity configuration, like so:
public class MyEntityConfiguration: IEntityTypeConfiguration<TodoTask>
{
public void Configure(EntityTypeBuilder<MyEntity> builder)
{
builder
.ToTable("MyEntities")
.IsMultiTenant()
.HasKey(x => x.MyEntityId);
}
}
- you can derive from
MultiTenantDbContext
in order to use multi-tenant capabilities:
public class MyDbContext : MultiTenantDbContext
{
public DbSet<MyEntity> MyEntities { get; set; }
public TodoDbContext(DbContextOptions<MyDbContext> options)
: base(options)
{
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.ApplyConfiguration(new MyEntityConfiguration());
base.OnModelCreating(modelBuilder);
}
}
- if you cannot derive from MultiTenantDbContext, you can add multi-tenant capabilities to your existing DbContext by overriding the methods
SaveChangesAsync
andSaveChanges
, like so:
public override Task<int> SaveChangesAsync(bool acceptAllChangesOnSuccess, CancellationToken cancellationToken = default)
{
this.SetTenantIdFromContext();
return base.SaveChangesAsync(acceptAllChangesOnSuccess, cancellationToken);
}
public override int SaveChanges(bool acceptAllChangesOnSuccess)
{
this.SetTenantIdFromContext();
return base.SaveChanges(acceptAllChangesOnSuccess);
}
When registering multi-tenant DbContext you can use the ITenantDatabaseConfigService
abstractions in order to get the tenant's connection string. You also need to call the UseMultitenancy
extension:
services.AddDbContext<KycDbContext>((serviceProvider, options) =>
{
var tenantConfiguration = serviceProvider.GetRequiredService<ITenantConfiguration>();
var connectionString = tenantConfiguration.GetConnectionString("DefaultConnection");
options
.UseSqlServer(connectionString)
.UseMultitenancy(serviceProvider);
});
Be aware that when using a multi-tenant dbContext, you cannot use the
DbContextPool