| name | abp-multi-tenancy |
| description | 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. |
ABP Multi-Tenancy
Docs: https://abp.io/docs/latest/framework/architecture/multi-tenancy
Making Entities Multi-Tenant
Implement IMultiTenant interface to make entities tenant-aware:
public class Product : AggregateRoot<Guid>, IMultiTenant
{
public Guid? TenantId { get; set; }
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;
}
}
Key points:
TenantId is nullable - null means entity belongs to Host
- ABP automatically filters queries by current tenant
- ABP automatically sets
TenantId when creating entities
Accessing Current Tenant
Use CurrentTenant property (available in base classes) or inject ICurrentTenant:
public class ProductAppService : ApplicationService
{
public async Task DoSomethingAsync()
{
var tenantId = CurrentTenant.Id;
var tenantName = CurrentTenant.Name;
var isAvailable = CurrentTenant.IsAvailable;
}
}
public class MyService : ITransientDependency
{
private readonly ICurrentTenant _currentTenant;
public MyService(ICurrentTenant currentTenant) => _currentTenant = currentTenant;
}
Switching Tenant Context
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)
{
using (CurrentTenant.Change(tenantId))
{
return await _productRepository.GetCountAsync();
}
}
public async Task DoHostOperationAsync()
{
using (CurrentTenant.Change(null))
{
}
}
}
Important: Always use Change() with a using statement.
Disabling Multi-Tenant Filter
To query all tenants' data (only works with single database):
public class ProductManager : DomainService
{
public async Task<long> GetAllProductCountAsync()
{
using (DataFilter.Disable<IMultiTenant>())
{
return await _productRepository.GetCountAsync();
}
}
}
Note: This doesn't work with separate databases per tenant.
Database Architecture Options
| 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.
Best Practices
- Always implement
IMultiTenant for tenant-specific entities
- Never manually filter by
TenantId - ABP does it automatically
- Don't change
TenantId after creation - it moves entity between tenants
- Use
Change() scope carefully - nested scopes are supported
- Test both host and tenant contexts - ensure proper data isolation
- Consider nullable
TenantId - entity may be host-only or shared
Enabling Multi-Tenancy
Configure<AbpMultiTenancyOptions>(options =>
{
options.IsEnabled = true;
});
Check MultiTenancyConsts.IsEnabled in your solution for centralized control.
Tenant Resolution
ABP resolves current tenant from (in order):
- Current user's claims
- Query string (
?__tenant=...)
- Route (
/{__tenant}/...)
- HTTP header (
__tenant)
- Cookie (
__tenant)
- Domain/subdomain (if configured)
For subdomain-based resolution:
Configure<AbpTenantResolveOptions>(options =>
{
options.AddDomainTenantResolver("{0}.mydomain.com");
});