Building Multi-Tenant SaaS with .NET and Azure
Multi-tenancy is the cornerstone of profitable SaaS economics. A single infrastructure serving thousands of customers is 10-20x more cost-efficient than dedicated instances per customer. According to Bessemer Venture Partners Cloud Index, successful SaaS companies achieve gross margins of 70-80%, largely due to efficient multi-tenant architectures.

I've architected multi-tenant SaaS platforms serving from 50 to 5,000+ tenants. This guide covers the architectural patterns, security considerations, database strategies, and Azure-specific implementations that make multi-tenant SaaS successful at scale.
01Multi-Tenancy Models
1. Shared Database, Shared Schema
All tenants share tables with a TenantId discriminator column. Simplest and most cost-effective model. Perfect for platforms using PostgreSQL databases, which offer excellent performance for large tenant counts.
✓ Pros
- • Lowest infrastructure cost
- • Simple deployments
- • Easy backups and maintenance
- • Efficient resource utilization
- • Horizontal scaling straightforward
✗ Cons
- • Risk of data leaks if filters missed
- • Noisy neighbor issues
- • Limited per-tenant customization
- • Tenant recovery is complex
- • Compliance challenges
Best for:
SMB SaaS with 100-10,000 tenants, similar usage patterns, low compliance requirements. Examples: project management tools, CRM for small businesses.
2. Shared Database, Separate Schemas
Single database with schema-per-tenant (PostgreSQL schemas or SQL Server schemas). Moderate isolation.
✓ Pros
- • Better tenant isolation
- • Schema-level permissions
- • Easier tenant backup/restore
- • Good cost-isolation balance
✗ Cons
- • Complex schema migrations
- • Database connection overhead
- • Limited scalability (few thousand schemas max)
- • Tenant onboarding slower
Best for:
Mid-market B2B SaaS with 10-1,000 tenants, moderate data volumes per tenant, some compliance needs. Examples: vertical SaaS for healthcare, legal.
3. Separate Databases (Database-per-Tenant)
Each tenant gets a dedicated database. Maximum isolation and customization.
✓ Pros
- • Maximum security isolation
- • Easy tenant backup/restore
- • Per-tenant customization
- • Compliance-friendly
- • Noisy neighbor isolation
✗ Cons
- • High infrastructure cost
- • Complex deployments
- • Schema migration overhead
- • Management complexity
- • Cross-tenant analytics hard
Best for:
Enterprise B2B SaaS with regulatory requirements, large data per tenant, custom SLAs. Examples: HR platforms, financial services SaaS.
4. Hybrid Model (Recommended for Growth)
Combine models based on tenant tier: shared database for SMB, dedicated databases for enterprise.
Tier 1 (Free/Starter): Shared DB, shared schema, 1,000+ tenants
Tier 2 (Professional): Shared DB, separate schemas, 100-500 tenants
Tier 3 (Enterprise): Dedicated databases, 10-50 tenants
This maximizes cost efficiency for small customers while offering isolation for enterprise customers willing to pay premium.
02Implementation with .NET and EF Core

Tenant Resolution Middleware
A critical component of any multi-tenant platform is proper tenant identification. In a modern .NET platform, middleware provides the ideal point to implement this logic.
// Tenant identification strategies
public class TenantResolutionMiddleware
{
public async Task InvokeAsync(HttpContext context, ITenantService tenantService)
{
// Strategy 1: Subdomain (tenant1.app.com)
var host = context.Request.Host.Host;
var tenantId = ExtractTenantFromSubdomain(host);
// Strategy 2: Header (X-Tenant-ID)
if (string.IsNullOrEmpty(tenantId))
tenantId = context.Request.Headers["X-Tenant-ID"];
// Strategy 3: JWT Claim
if (string.IsNullOrEmpty(tenantId) && context.User.Identity?.IsAuthenticated == true)
tenantId = context.User.FindFirst("tenant_id")?.Value;
// CRITICAL: Never trust client-sent tenant IDs for data access
// Always validate against authenticated user's allowed tenants
var tenant = await tenantService.GetAndValidateTenantAsync(tenantId, context.User);
context.Items["TenantId"] = tenant.Id;
}
}Global Query Filters (EF Core)
Automatically filter all queries by tenant to prevent data leaks:
public class ApplicationDbContext : DbContext
{
private readonly ITenantService _tenantService;
public ApplicationDbContext(DbContextOptions options, ITenantService tenantService)
: base(options)
{
_tenantService = tenantService;
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
// Apply global query filter to all tenant-scoped entities
foreach (var entityType in modelBuilder.Model.GetEntityTypes())
{
if (typeof(ITenantEntity).IsAssignableFrom(entityType.ClrType))
{
var method = typeof(ApplicationDbContext)
.GetMethod(nameof(SetGlobalQueryFilter), BindingFlags.NonPublic | BindingFlags.Static)
?.MakeGenericMethod(entityType.ClrType);
method?.Invoke(null, new object[] { modelBuilder, _tenantService });
}
}
}
private static void SetGlobalQueryFilter<T>(ModelBuilder modelBuilder, ITenantService tenantService)
where T : class, ITenantEntity
{
modelBuilder.Entity<T>().HasQueryFilter(e => e.TenantId == tenantService.GetCurrentTenantId());
}
}
// Entity interface
public interface ITenantEntity
{
string TenantId { get; set; }
}
// Example entity
public class Order : ITenantEntity
{
public int Id { get; set; }
public string TenantId { get; set; } // Automatically filtered
public decimal Amount { get; set; }
}Database-per-Tenant with Connection String Resolution
public class TenantDbContextFactory
{
private readonly ITenantService _tenantService;
private readonly IConfiguration _configuration;
public ApplicationDbContext Create()
{
var tenantId = _tenantService.GetCurrentTenantId();
var connectionString = GetConnectionStringForTenant(tenantId);
var optionsBuilder = new DbContextOptionsBuilder<ApplicationDbContext>();
optionsBuilder.UseNpgsql(connectionString);
return new ApplicationDbContext(optionsBuilder.Options);
}
private string GetConnectionStringForTenant(string tenantId)
{
// Option 1: Dedicated database per tenant
return $"Host=postgres.app.com;Database=tenant_{tenantId};Username=app;Password=...";
// Option 2: Azure SQL Elastic Pool with databases
// return $"Server=tcp:app.database.windows.net;Database=tenant_{tenantId};...";
// Option 3: Lookup from tenant configuration table
// var tenant = await _tenantRepo.GetAsync(tenantId);
// return tenant.ConnectionString;
}
}03Azure Services for Multi-Tenant SaaS

Whether using Azure Kubernetes deployment or traditional App Service, Azure offers a range of services ideal for multi-tenant platforms. The key is cloud cost optimization through proper tier selection and auto-scaling.
Azure SQL Elastic Pools
Share compute resources across multiple tenant databases while maintaining isolation.
S3 Elastic Pool
100 eDTUs, $200/month
~50 small databases (2 eDTU each)
S6 Elastic Pool
400 eDTUs, $800/month
~100 medium databases (4 eDTU avg)
S12 Elastic Pool
3000 eDTUs, $6,000/month
~500 databases with bursting
Azure AD B2C for Multi-Tenant Auth
Manage tenant users with Azure AD B2C: custom policies, social login, MFA, user flows.
- • Store tenant context in JWT claims
- • Support invite-based user provisioning
- • Enable SSO for enterprise tenants
- • Pricing: First 50K MAU free, then $0.00325/MAU
Application Insights Per-Tenant Telemetry
// Add tenant context to all telemetry
services.AddApplicationInsightsTelemetry();
services.AddSingleton<ITelemetryInitializer, TenantTelemetryInitializer>();
public class TenantTelemetryInitializer : ITelemetryInitializer
{
private readonly ITenantService _tenantService;
public void Initialize(ITelemetry telemetry)
{
if (telemetry is ISupportProperties propertiesTelemetry)
{
propertiesTelemetry.Properties["TenantId"] = _tenantService.GetCurrentTenantId();
propertiesTelemetry.Properties["TenantName"] = _tenantService.GetCurrentTenantName();
}
}
}
// Query in Azure portal:
// requests | where customDimensions.TenantId == "tenant-123" | summarize count() by bin(timestamp, 1h)04Scaling and Performance

Scaling a multi-tenant platform requires a thoughtful strategy. In a microservices architecture, each component can scale independently, which ideally fits multi-tenant models with varying load per tenant.
Connection Pooling
Problem: Database-per-tenant creates connection overhead
Solution: Use connection string-based pooling with max pool size limits
// .NET connection string
"...;Max Pool Size=20;Min Pool Size=2;"20 connections × 100 databases = 2,000 max connections. Monitor with PgBouncer or Azure SQL monitoring.
Caching Per-Tenant
Use Redis with tenant-prefixed keys:
var cacheKey = $"tenant:{tenantId}:orders:{orderId}";
await _cache.SetStringAsync(cacheKey, json,
new DistributedCacheEntryOptions {
AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(10)
});Enables per-tenant cache invalidation and prevents cross-tenant cache pollution.
Database Sharding
For 1,000+ tenants, shard across multiple database servers:
- • Shard 1: Tenants 1-500 → postgres-01
- • Shard 2: Tenants 501-1000 → postgres-02
- • Route based on consistent hashing of tenant ID
- • Use Azure Database for PostgreSQL with read replicas
Noisy Neighbor Prevention
Implement rate limiting per tenant:
[TenantRateLimit(MaxRequests = 1000, WindowMinutes = 1)]
public async Task<IActionResult> GetOrders()
{
// Tenant automatically limited to 1000 req/min
}Use Redis for distributed rate limiting across multiple app instances.
05Real-World Implementation

B2B Project Management SaaS
Architecture Choices
- • Hybrid model: shared DB for SMB, dedicated for enterprise
- • 850 SMB tenants in shared PostgreSQL (2TB total)
- • 15 enterprise tenants with dedicated databases
- • Azure App Service P1V3 with autoscaling (2-10 instances)
- • Azure SQL Elastic Pool S9 (1600 eDTUs)
- • Redis Premium P1 for caching and rate limiting
- • Real-time communication with SignalR for team collaboration
Monthly Costs
Cost per tenant: $9.50/month (850 SMB + 15 enterprise). MRR: $45K. Infrastructure: 18% of revenue.
Key Metrics After 18 Months
99.97%
Uptime (SLA: 99.9%)
Zero
Cross-tenant data leaks
850 →
2,100 tenants (147% growth)
06FAQs
Which multi-tenancy model should I choose?
Choose based on scale and isolation needs: Shared database/shared schema for 100-10,000 tenants (lowest cost), shared database/separate schemas for 10-1,000 tenants (moderate isolation), separate databases for enterprise tenants (maximum isolation). Most successful SaaS platforms use hybrid approach: shared for SMB, dedicated for enterprise.
How do I handle tenant-specific customizations?
Use configuration-driven approach: store tenant preferences in metadata tables, use feature flags for per-tenant features, implement plugin architecture for custom business logic. Avoid tenant-specific code branches. Instead, use strategy pattern with dependency injection to swap implementations per tenant.
What are the security best practices?
Critical practices: (1) Never trust client-sent tenant IDs, always derive from authenticated identity, (2) Use EF Core global query filters to prevent cross-tenant data leaks, (3) Implement tenant context middleware, (4) Encrypt sensitive data with tenant-specific keys, (5) Audit cross-tenant operations, (6) Test for tenant isolation with automated tests, (7) Implement rate limiting per tenant.
How much does it cost to run on Azure?
Small SaaS (100 tenants): $500-1,500/month. Medium SaaS (1,000 tenants): $3,000-8,000/month. Large SaaS (10K+ tenants): $20K-50K+/month. Key is proper tier selection and autoscaling. Use Azure Cost Management for monitoring and reserved instances for 40% savings on predictable workloads.
Can I migrate from single-tenant to multi-tenant?
Yes, phased approach: (1) Add tenant context to tables, (2) Implement tenant resolution middleware, (3) Update code to filter by tenant ID, (4) Consolidate databases with data migration, (5) Test for data leaks, (6) Run parallel for validation. Expect 3-6 months for production migration depending on codebase size.
Building Multi-Tenant SaaS?
I help SaaS companies design and implement scalable multi-tenant architectures with .NET and Azure. Let's discuss your SaaS architecture.
Related Articles
References
- [1] Microsoft Azure - Official Documentation -https://learn.microsoft.com/en-us/azure/
- [2] Microsoft Learn - Azure Training Center -https://learn.microsoft.com/en-us/training/azure/
- [3] Kubernetes - Official Documentation -https://kubernetes.io/docs/
- [4] CNCF Annual Survey 2023 - State of Kubernetes Adoption -https://www.cncf.io/reports/cncf-annual-survey-2023/
- [5] .NET - Official Microsoft Documentation -https://learn.microsoft.com/en-us/dotnet/
- [6] .NET Blog - Latest updates and best practices -https://devblogs.microsoft.com/dotnet/
- [7] Flexera State of the Cloud Report 2024 -https://www.flexera.com/blog/cloud/cloud-computing-trends-2024-state-of-the-cloud-report/
- [8] FinOps Foundation - Best Practices -https://www.finops.org/
- [9] Gartner - Cloud Computing Research -https://www.gartner.com/en/information-technology/insights/cloud-computing