Thanks for the detailed explanation. That makes sense now and it also explains why none of the approaches I tried had any effect.
We'll go ahead with the custom per-tenant worker and use our tenant-aware DbContext to manage the background jobs instead of the built-in store.
Just out of curiosity, is there any plan to support tenant-specific background job stores (for database-per-tenant scenarios) in a future ABP release?
Hi,
We're implementing a database-per-tenant architecture where each tenant has its own SQL Server database with its own AbpBackgroundJobs table. We need our custom background job worker to poll each tenant's job queue independently.
Setup:
• ABP v10.3.0 (EF Core, SQL Server) • Each tenant has a "Default" connection string configured via SaasTenantConnectionStrings • The tenant database contains an AbpBackgroundJobs table with waiting jobs (confirmed via SSMS)
────────────────────
Original Approach:
Our custom AsyncPeriodicBackgroundWorkerBase switches tenant context before polling:
using (_currentTenant.Change(tenantId)) { using var scope = serviceProvider.CreateScope(); var jobStore = scope.ServiceProvider.GetRequiredService<IBackgroundJobStore>();
var jobs = await jobStore.GetWaitingJobsAsync(null, 100);
// Returns 0 — despite waiting jobs existing in the tenant DB
}
We expected ICurrentTenant.Change(tenantId) to cause the EfCoreBackgroundJobStore to resolve the tenant's connection string (as it does for all our other DbContext queries), but it always queries the host database.
────────────────────
Diagnostic proof that the scope IS switching correctly:
using (_currentTenant.Change(tenantId)) { using var scope = serviceProvider.CreateScope(); var uowManager = scope.ServiceProvider.GetRequiredService<IUnitOfWorkManager>(); using var uow = uowManager.Begin(requiresNew: true, isTransactional: false);
// ✅ Connection string resolver returns tenant DB
var resolver = scope.ServiceProvider.GetRequiredService<IConnectionStringResolver>();
var connStr = await resolver.ResolveAsync(); // → "...Initial Catalog=db-tenant..."
// ✅ Our AppDbContext connects to tenant DB
var dbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
var actualDb = dbContext.Database.GetDbConnection().Database; // → "db-tenant"
// ✅ Direct EF query finds the job
var directJobs = await dbContext.BackgroundJobs
.Where(j => !j.IsAbandoned && j.NextTryTime <= now)
.ToListAsync(); // → Count: 1
// ❌ IBackgroundJobStore still returns 0
var jobStore = scope.ServiceProvider.GetRequiredService<IBackgroundJobStore>();
var storeJobs = await jobStore.GetWaitingJobsAsync(null, 100); // → Count: 0
}
This proves the tenant context switch works for our AppDbContext but IBackgroundJobStore ignores it entirely.
────────────────────
What we've tried (all unsuccessful):
3. IDataFilter suppression — Wrapped the call in using (_dataFilter.Disable<IMultiTenant>()) to bypass tenant filtering on the query. ❌ (This returned jobs from the host DB instead)
4. New UnitOfWork — Created a fresh scope + IUnitOfWorkManager.Begin(requiresNew: true) to force EF Core to re-evaluate the connection string. ❌
5. Named connection string on tenant — Added "AbpBackgroundJobs" as a named connection string on the tenant record (in addition to "Default"). ❌
────────────────────
Question:
Is there a supported way to make IBackgroundJobStore (specifically EfCoreBackgroundJobStore ) resolve to the tenant's database when ICurrentTenant is switched programmatically in a background worker context?
Or is the expected approach for DB-per-tenant job processing to bypass IBackgroundJobStore entirely and query the BackgroundJobRecord DbSet directly via our own DbContext (which does resolve correctly)?
Thank you.
Hi,
We're implementing a database-per-tenant architecture where each tenant has its own SQL Server database with its own AbpBackgroundJobs table. We need our custom background job worker to poll each tenant's job queue independently.
Setup:
• ABP Commercial 5.x (EF Core, SQL Server) • Each tenant has a "Default" connection string configured via SaasTenantConnectionStrings • The tenant database contains an AbpBackgroundJobs table with waiting jobs (confirmed via SSMS)
────────────────────
Original Approach:
Our custom AsyncPeriodicBackgroundWorkerBase switches tenant context before polling:
using (_currentTenant.Change(tenantId)) { using var scope = serviceProvider.CreateScope(); var jobStore = scope.ServiceProvider.GetRequiredService<IBackgroundJobStore>();
var jobs = await jobStore.GetWaitingJobsAsync(null, 100);
// Returns 0 — despite waiting jobs existing in the tenant DB
}
We expected ICurrentTenant.Change(tenantId) to cause the EfCoreBackgroundJobStore to resolve the tenant's connection string (as it does for all our other DbContext queries), but it always queries the host database.
────────────────────
Diagnostic proof that the scope IS switching correctly:
using (_currentTenant.Change(tenantId)) { using var scope = serviceProvider.CreateScope(); var uowManager = scope.ServiceProvider.GetRequiredService<IUnitOfWorkManager>(); using var uow = uowManager.Begin(requiresNew: true, isTransactional: false);
// ✅ Connection string resolver returns tenant DB
var resolver = scope.ServiceProvider.GetRequiredService<IConnectionStringResolver>();
var connStr = await resolver.ResolveAsync(); // → "...Initial Catalog=db-tenant..."
// ✅ Our AppDbContext connects to tenant DB
var dbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
var actualDb = dbContext.Database.GetDbConnection().Database; // → "db-tenant"
// ✅ Direct EF query finds the job
var directJobs = await dbContext.BackgroundJobs
.Where(j => !j.IsAbandoned && j.NextTryTime <= now)
.ToListAsync(); // → Count: 1
// ❌ IBackgroundJobStore still returns 0
var jobStore = scope.ServiceProvider.GetRequiredService<IBackgroundJobStore>();
var storeJobs = await jobStore.GetWaitingJobsAsync(null, 100); // → Count: 0
}
This proves the tenant context switch works for our AppDbContext but IBackgroundJobStore ignores it entirely.
────────────────────
What we've tried (all unsuccessful):
3. IDataFilter suppression — Wrapped the call in using (_dataFilter.Disable<IMultiTenant>()) to bypass tenant filtering on the query. ❌ (This returned jobs from the host DB instead)
4. New UnitOfWork — Created a fresh scope + IUnitOfWorkManager.Begin(requiresNew: true) to force EF Core to re-evaluate the connection string. ❌
5. Named connection string on tenant — Added "AbpBackgroundJobs" as a named connection string on the tenant record (in addition to "Default"). ❌
────────────────────
Question:
Is there a supported way to make IBackgroundJobStore (specifically EfCoreBackgroundJobStore ) resolve to the tenant's database when ICurrentTenant is switched programmatically in a background worker context?
Or is the expected approach for DB-per-tenant job processing to bypass IBackgroundJobStore entirely and query the BackgroundJobRecord DbSet directly via our own DbContext (which does resolve correctly)?
Thank you.
It seems that Language Texts will need to be specified for each Tenant. We would like to change the terminology of "OrganizationUnit" to "Company" for all Tenants within the system without having to configure this individually.
Is it possible to set Language Texts at the host level, which will then be applied to all Tenants created by the host?