Hi,
Glad that cleared it up.
The built-in background job store is host-only by design, and there's no committed plan or release target for a tenant-specific / database-per-tenant store right now. If that changes it'll show up in the release notes.
For now, the custom per-tenant worker over your tenant-aware DbContext is the way to go.
Thanks
You're welcome. Whenever you're ready, just share your project structure (version, template, single-project vs separated) and whether you still need a browser login, and I'll send you the exact module/package changes for your project.
Thanks
Hi,
This is by design, and it's actually two things stacking up, which is why none of your five attempts moved the needle:
DbContext is marked [IgnoreMultiTenancy], and BackgroundJobRecord isn't a multi-tenant entity (no TenantId). The module is meant to live in the host database.DbContext, it temporarily switches ICurrentTenant to null for entities that aren't IMultiTenant. BackgroundJobRecord isn't, so the connection string is pinned to host regardless of what tenant you switched to.Because of point 2, ReplaceDbContext can't help — even if you point the store at a tenant-aware context, the repository still forces the tenant to null for this entity type. And the connection-string mapping / named-connection attempts never get consulted, since the tenant is already null by the time resolution runs.
To answer your last question: yes, for DB-per-tenant job queues you route this through a tenant-aware DbContext and bypass the host-pinning. The cleanest way is to replace IBackgroundJobRepository with one bound to your AppDbContext (which already resolves the tenant connection and has the table mapped), and override GetDbContextAsync so it stops resetting the tenant:
[Dependency(ReplaceServices = true)]
[ExposeServices(typeof(IBackgroundJobRepository))]
public class TenantAwareBackgroundJobRepository
: EfCoreRepository<AppDbContext, BackgroundJobRecord, Guid>, IBackgroundJobRepository
{
private readonly IDbContextProvider<AppDbContext> _dbContextProvider;
protected IClock Clock { get; }
public TenantAwareBackgroundJobRepository(
IDbContextProvider<AppDbContext> dbContextProvider,
IClock clock)
: base(dbContextProvider)
{
_dbContextProvider = dbContextProvider;
Clock = clock;
}
// BackgroundJobRecord is not IMultiTenant, so the base repository would switch the
// current tenant to null and read from the host DB. Keep the current tenant instead
// so the connection string follows it.
protected override Task<AppDbContext> GetDbContextAsync()
{
return _dbContextProvider.GetDbContextAsync();
}
public virtual async Task<List<BackgroundJobRecord>> GetWaitingListAsync(
string applicationName,
int maxResultCount,
CancellationToken cancellationToken = default)
{
var now = Clock.Now;
return await (await GetDbSetAsync())
.Where(t => t.ApplicationName == applicationName)
.Where(t => !t.IsAbandoned && t.NextTryTime <= now)
.OrderByDescending(t => t.Priority)
.ThenBy(t => t.TryCount)
.ThenBy(t => t.NextTryTime)
.Take(maxResultCount)
.ToListAsync(GetCancellationToken(cancellationToken));
}
}
The built-in BackgroundJobStore just delegates to IBackgroundJobRepository, so once this replacement is in place, IBackgroundJobStore.GetWaitingJobsAsync reads from the tenant DB when you switch tenant in your worker. No new DbContext and no extra migration — you're reusing the per-tenant table you already have.
One thing to keep in mind: this makes the whole store tenant-aware, so enqueue/update/delete follow the current tenant too. Jobs added while a tenant is current get written to that tenant's DB, which is what you want for per-tenant queues. The built-in BackgroundJobWorker still runs as host and only picks up host jobs, so polling each tenant DB stays the job of your own worker that switches ICurrentTenant — which is exactly what you're already doing.
Thanks
Hi,
This is by design, not something you can toggle. The background jobs store is host-only.
IBackgroundJobsDbContext is marked with [IgnoreMultiTenancy], and when the connection string is resolved for such a context, the framework forces CurrentTenant.Change(null) before resolving:
// UnitOfWorkDbContextProvider.ResolveConnectionStringAsync
if (typeof(TDbContext).IsDefined(typeof(IgnoreMultiTenancyAttribute), false))
{
using (CurrentTenant.Change(null)) // always host
{
return await ConnectionStringResolver.ResolveAsync(connectionStringName);
}
}
EfCoreBackgroundJobRepository is bound to IBackgroundJobsDbContext, so TDbContext here is that interface and it always carries [IgnoreMultiTenancy]. That's why GetWaitingJobsAsync always hits the host DB even though your own AppDbContext resolves to the tenant DB in the same scope.
That's also why none of your five attempts changed anything — the connection string is resolved under the host tenant, before anything tenant-specific is looked at:
ReplaceDbContext does swap in your concrete DbContext, but the [IgnoreMultiTenancy] check runs against the repository's generic type IBackgroundJobsDbContext, not against your replacement, so the connection string is still resolved as host.AbpDbConnectionOptions remapping and the named AbpBackgroundJobs connection string on the tenant are never consulted, because the current tenant is already null at resolve time.BackgroundJobRecord isn't IMultiTenant (there's no TenantId column), so the IDataFilter suppression has nothing to disable — you just got host rows back.So there's no supported way to make IBackgroundJobStore / EfCoreBackgroundJobStore resolve to the tenant database. For a DB-per-tenant setup where the jobs physically live in each tenant DB, your second option is the right one: query BackgroundJobRecord directly through your own tenant-aware DbContext (the one that already resolves correctly):
using (_currentTenant.Change(tenantId))
{
using var scope = serviceProvider.CreateScope();
using var uow = scope.ServiceProvider
.GetRequiredService<IUnitOfWorkManager>()
.Begin(requiresNew: true, isTransactional: false);
var dbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
var now = Clock.Now;
var waitingJobs = await dbContext.BackgroundJobs
.Where(j => !j.IsAbandoned && j.NextTryTime <= now)
.OrderByDescending(j => j.Priority)
.ThenBy(j => j.TryCount)
.ThenBy(j => j.NextTryTime)
.Take(100)
.ToListAsync();
// process, then update TryCount / NextTryTime / IsAbandoned
// through the same dbContext
await uow.CompleteAsync();
}
Keep the filter in sync with your ABP version's EfCoreBackgroundJobRepository.GetWaitingListAsync so the semantics match — depending on the version it may also filter on ApplicationName and CompletionTime, and those columns may not exist on older ones, so mirror exactly what your version has.
One thing to keep in mind for the whole pipeline: enqueue and update go through the same host-only store, so IBackgroundJobManager / IBackgroundJobStore write to the host DB too. If your jobs need to live in the tenant DB, insert and update them through this tenant-aware DbContext as well, not through the standard manager. And the built-in BackgroundJobWorker only polls the host store, so your own per-tenant worker is exactly the pattern to use here.
Thanks
Hi,
Yes, this is doable. The clean ABP setup for it is the separated (non-bundled) architecture: the API host references your HttpApi / Application / EF layers plus JWT bearer auth, and it does not include the LeptonX theme or the Account login UI. That already removes the whole Lepton / MVC UI surface, which is the main thing you're after.
Two things worth knowing so there are no surprises:
*.Mvc.UI.* modules, not just the LeptonX theme.To give you the exact modules and packages to strip out for your project, could you share:
HttpApi.Host / AuthServer projects?With that I'll give you the precise list of module DependsOn entries and PackageReferences to remove.
Thanks
Hi,
This is fixed now - the missing source packages are published, so you can download the LeptonX theme source from ABP Studio/CLI again. Please give it another try.
Thanks
Hi,
This is fixed now - the missing source packages are published, so you can download the LeptonX theme source from ABP Studio/CLI again. Please give it another try.
Thanks
Hi,
This is fixed now - the missing source packages are published, so you can download the LeptonX theme source from ABP Studio/CLI again. Please give it another try.
Thanks
Hi,
This is fixed now - the missing source packages are published, so you can download the LeptonX theme source from ABP Studio/CLI again. Please give it another try.
Thanks
Hi,
This isn't an entitlement issue with your account - the source package for that LeptonX theme version wasn't published on our side, which is why Studio shows that message. We've confirmed it and will fix it soon.
We've refunded your ticket.
Thanks