Multi-tenancy is one of those architectural decisions that's easy to get wrong and expensive to change. The model you choose at the start shapes your data isolation guarantees, your deployment complexity, your cost structure, and your compliance posture for the next several years.
The three models
There are three common approaches to multi-tenancy in a relational database system:
- —Shared schema — all tenants share tables, rows distinguished by a tenant_id column
- —Schema-per-tenant — each tenant gets their own schema within a shared database
- —Database-per-tenant — each tenant gets a completely isolated database instance
Shared schema: simple to start, hard to secure
Shared schema is the default for most SaaS products. It's cheap to operate and simple to migrate. The risk is data bleed — a missing WHERE clause in a query can expose one tenant's data to another. At scale, row-level security (RLS) at the database level helps, but it adds operational complexity.
Tenant isolation via EF Core global query filter
// Register in DbContext.OnModelCreating
modelBuilder.Entity<Project>()
.HasQueryFilter(p => p.TenantId == _currentTenant.Id);
// Every query automatically scoped — but requires discipline
// to not bypass with IgnoreQueryFilters() accidentallyWhat we use and why
For most engagements, shared schema with strict EF Core global query filters and an integration test suite that verifies tenant isolation is the right tradeoff. It's the easiest to operate and the easiest to reason about when something goes wrong.
Database-per-tenant becomes worth considering when you have compliance requirements (SOC 2, HIPAA, GDPR with strict data residency) or when a single enterprise tenant is generating enough revenue to justify the operational overhead.