UZMAN
Multi-Tenancy Patterns
Tek uygulama ile birden fazla kiracıya (müşteri/organizasyon) hizmet vermek. Üç yaklaşım: aynı tabloda discriminator sütunu (basit), kiracı başına ayrı schema (orta), kiracı başına ayrı veritabanı (tam izolasyon).
Pattern 1: Discriminator (Aynı Tablo — Global Filter)
// Tenant bilgisi
public interface ITenantEntity
{
string TenantId { get; set; }
}
// Entities
public class Product : ITenantEntity
{
public int Id { get; set; }
public string Name { get; set; } = null!;
public string TenantId { get; set; } = null!; // Her satırda tenant bilgisi
}
// Tenant servis
public interface ITenantService
{
string GetCurrentTenantId();
}
// DbContext
public class MultiTenantDbContext : DbContext
{
private readonly string _tenantId;
public MultiTenantDbContext(
DbContextOptions options,
ITenantService tenantService) : base(options)
{
_tenantId = tenantService.GetCurrentTenantId();
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
// Tüm tenant entity'lerine global filter
modelBuilder.Entity<Product>()
.HasQueryFilter(p => p.TenantId == _tenantId);
modelBuilder.Entity<Order>()
.HasQueryFilter(o => o.TenantId == _tenantId);
}
public override async Task<int> SaveChangesAsync(CancellationToken ct = default)
{
// Yeni kayıtlara otomatik tenant ata
foreach (var entry in ChangeTracker.Entries<ITenantEntity>()
.Where(e => e.State == EntityState.Added))
{
entry.Entity.TenantId = _tenantId;
}
return await base.SaveChangesAsync(ct);
}
}
Pattern 2: Ayrı Veritabanı (Database per Tenant)
// Her tenant'ın kendi connection string'i var
public class TenantDbContextFactory
{
private readonly ITenantService _tenantService;
private readonly IConfiguration _config;
public AppDbContext CreateContext()
{
var tenantId = _tenantService.GetCurrentTenantId();
var connectionString = _config.GetConnectionString($"Tenant_{tenantId}");
var options = new DbContextOptionsBuilder<AppDbContext>()
.UseSqlServer(connectionString)
.Options;
return new AppDbContext(options);
}
}
// Program.cs
builder.Services.AddDbContext<AppDbContext>((sp, options) =>
{
var tenantService = sp.GetRequiredService<ITenantService>();
var tenantId = tenantService.GetCurrentTenantId();
var connectionString = configuration.GetConnectionString($"Tenant_{tenantId}");
options.UseSqlServer(connectionString);
});
Pattern 3: Ayrı Schema (Schema per Tenant)
// Her tenant farklı schema kullanır
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.HasDefaultSchema(_tenantId); // "tenant_abc" schema
}
Karşılaştırma
| Kriter | Discriminator | Ayrı DB | Ayrı Schema |
|---|---|---|---|
| Veri izolasyonu | Yazılımsal | Tam | DB-seviye |
| Maliyet | Düşük | Yüksek | Orta |
| Migration | Tek sefer | Her tenant | Her schema |
| Ölçekleme | Tek DB limiti | Bağımsız | Tek DB limiti |
| Backup/Restore | Hepsi birlikte | Bağımsız | Schema bazlı |