ABP Multi-Tenancy - IMultiTenant interface, CurrentTenant, CurrentTenant.Change(), DataFilter.Disable(IMultiTenant), tenant resolution order, database-per-tenant. Use when working with multi-tenant features, tenant-specific data isolation, or switching tenant context.
Docs: https://abp.io/docs/latest/framework/architecture/multi-tenancy
Implement IMultiTenant interface to make entities tenant-aware:
public class Product : AggregateRoot<Guid>, IMultiTenant
{
public Guid? TenantId { get; set; } // Required by IMultiTenant
public string Name { get; private set; }
public decimal Price { get; private set; }
protected Product() { }
public Product(Guid id, string name, decimal price) : base(id)
{
Name = name;
Price = price;
// TenantId is automatically set from CurrentTenant.Id
}
}
Key points:
TenantId is nullable - null means entity belongs to HostTenantId when creating entitiesUse CurrentTenant property (available in base classes) or inject ICurrentTenant:
public class ProductAppService : ApplicationService
{
public async Task DoSomethingAsync()
{
// Available from base class
var tenantId = CurrentTenant.Id; // Guid? - null for host
var tenantName = CurrentTenant.Name; // string?
var isAvailable = CurrentTenant.IsAvailable; // true if Id is not null
}
}
// In other services
public class MyService : ITransientDependency
{
private readonly ICurrentTenant _currentTenant;
public MyService(ICurrentTenant currentTenant) => _currentTenant = currentTenant;
}
Use CurrentTenant.Change() to temporarily switch tenant (useful in host context):
public class ProductManager : DomainService
{
private readonly IRepository<Product, Guid> _productRepository;
public async Task<long> GetProductCountAsync(Guid? tenantId)
{
// Switch to specific tenant
using (CurrentTenant.Change(tenantId))
{
return await _productRepository.GetCountAsync();
}
// Automatically restored to previous tenant after using block
}
public async Task DoHostOperationAsync()
{
// Switch to host context
using (CurrentTenant.Change(null))
{
// Operations here are in host context
}
}
}
Important: Always use
Change()with ausingstatement.
To query all tenants' data (only works with single database):
public class ProductManager : DomainService
{
public async Task<long> GetAllProductCountAsync()
{
// DataFilter is available from base class
using (DataFilter.Disable<IMultiTenant>())
{
return await _productRepository.GetCountAsync();
// Returns count from ALL tenants
}
}
}
Note: This doesn't work with separate databases per tenant.
| Approach | Description | Use Case |
|---|---|---|
| Single Database | All tenants share one database | Simple, cost-effective |
| Database per Tenant | Each tenant has dedicated database | Data isolation, compliance |
| Hybrid | Mix of shared and dedicated | Flexible, premium tenants |
Connection strings are configured per tenant in Tenant Management module.
IMultiTenant for tenant-specific entitiesTenantId - ABP does it automaticallyTenantId after creation - it moves entity between tenantsChange() scope carefully - nested scopes are supportedTenantId - entity may be host-only or sharedConfigure<AbpMultiTenancyOptions>(options =>
{
options.IsEnabled = true; // Enabled by default in ABP templates
});
Check MultiTenancyConsts.IsEnabled in your solution for centralized control.
ABP resolves current tenant from (in order):
?__tenant=...)/{__tenant}/...)__tenant)__tenant)For subdomain-based resolution:
Configure<AbpTenantResolveOptions>(options =>
{
options.AddDomainTenantResolver("{0}.mydomain.com");
});