From ABP Sensei
Guides ABP Framework multi-tenancy: tenant resolver, ICurrentTenant, IMultiTenant, database isolation, and data filtering. For SaaS and tenant management.
How this skill is triggered — by the user, by Claude, or both
Slash command
/abp-sensei:abp-multitenancyThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
ABP Framework v10.4 multi-tenancy (SaaS) implementation guide. Tenant resolver, IMultiTenant, ICurrentTenant, database isolation.
ABP Framework v10.4 multi-tenancy (SaaS) implementation guide. Tenant resolver, IMultiTenant, ICurrentTenant, database isolation.
Configure<AbpMultiTenancyOptions>(options =>
{
options.IsEnabled = true;
});
In the startup templates this is controlled from a single point via the
MultiTenancyConstsclass.
| Approach | Description |
|---|---|
| Single Database | All tenants in the same DB, separated by TenantId |
| Database per Tenant | Each tenant has its own DB |
| Hybrid | Some tenants share a DB, others have a separate DB |
public class Product : AggregateRoot<Guid>, IMultiTenant
{
public Guid? TenantId { get; set; } // From the IMultiTenant interface
public string Name { get; set; }
public float Price { get; set; }
}
Important:
TenantId is nullable — if null, the entity belongs to the HostTenantId is set automatically (from ICurrentTenant.Id)// Properties
CurrentTenant.Id // Guid? — Current tenant ID
CurrentTenant.Name // string — Current tenant name
CurrentTenant.IsAvailable // bool — true if ID is not null
// Changing the tenant (scoped)
using (CurrentTenant.Change(tenantId))
{
// Within this scope, operations are performed on behalf of the specified tenant
var count = await _productRepository.GetCountAsync();
}
// Switch to the host context
using (CurrentTenant.Change(null))
{
// Operation in the host context
}
public class ProductManager : DomainService
{
private readonly IRepository<Product, Guid> _productRepository;
private readonly IDataFilter _dataFilter;
public ProductManager(IRepository<Product, Guid> productRepository, IDataFilter dataFilter)
{
_productRepository = productRepository;
_dataFilter = dataFilter;
}
public async Task<long> GetAllProductCountAsync()
{
using (_dataFilter.Disable<IMultiTenant>())
{
return await _productRepository.GetCountAsync();
}
}
}
?__tenant=xxx__tenant)__tenant)Configure<AbpAspNetCoreMultiTenancyOptions>(options =>
{
options.TenantKey = "MyTenantKey";
});
// Subdomain: mytenant.mydomain.com
Configure<AbpTenantResolveOptions>(options =>
{
options.AddDomainTenantResolver("{0}.mydomain.com");
});
// OpenIddict wildcard domain (if a separate Auth Server is used)
PreConfigure<AbpOpenIddictWildcardDomainOptions>(options =>
{
options.EnableWildcardDomainSupport = true;
options.WildcardDomainsFormat.Add("https://{0}.mydomain.com");
});
public class MyCustomTenantResolveContributor : TenantResolveContributorBase
{
public override string Name => "Custom";
public override Task ResolveAsync(ITenantResolveContext context)
{
// Set context.TenantIdOrName
// Use DI via context.ServiceProvider
return Task.CompletedTask;
}
}
// Registration
Configure<AbpTenantResolveOptions>(options =>
{
options.TenantResolvers.Add(new MyCustomTenantResolveContributor());
});
Configure<AbpTenantResolveOptions>(options =>
{
options.FallbackTenant = "acme"; // Used when no tenant is found
});
app.UseAuthentication();
app.UseMultiTenancy(); // Immediately after authentication
Already configured in the startup templates.
Included in the startup templates. The ITenantStore implementation fetches tenant information from the DB.
// appsettings.json
"Tenants": [
{
"Id": "446a5211-3d72-4339-9adc-845151f8ada0",
"Name": "tenant1",
"NormalizedName": "TENANT1"
},
{
"Id": "25388015-ef1c-4355-9c18-f6b6ddbaf89d",
"Name": "tenant2",
"NormalizedName": "TENANT2",
"ConnectionStrings": {
"Default": "...tenant2's connection string..."
}
}
]
[IgnoreMultiTenancy] // Always uses the host DB
public class TenantManagementDbContext : AbpDbContext<TenantManagementDbContext> { }
In ABP, the following services are designed to be multi-tenancy-aware:
IMultiTenant — For tenant-specific entitiesCurrentTenant.Change with using — The previous value is restored outside the scopeCurrentUserTenantResolveContributor must always be firstCurrentTenant.Change(null) for host-side operationsappsettings.json approach is only for simple scenariosnpx claudepluginhub burakdmir/abp-skills --plugin abp-senseiProvides CDSS development patterns for drug interaction checking, dose validation, clinical scoring (NEWS2, qSOFA), and alert classification integrated into EMR workflows.