Hi,
In my multi-tenant application, when I assign a different database for a tenant and obtain a new DbContext instance using the GetDbContextAsync() method in repositories, the connection string always points to the host database instead of the tenant database. I don’t understand why this happens. Below are some details that might help clarify the situation — I’d appreciate your comments based on them.
MultiTenancy is enabled.
The entity implements the IMultiTenant interface.
dbContext.CurrentTenant has the correct value.
When I call tenantStore.FindAsync, the tenant configuration is returned correctly.
In the container, I can see that IConnectionStringResolver is registered as MultiTenantConnectionStringResolver.
var connectionStringName = ConnectionStringNameAttribute.GetConnStringName(typeof(xxxx)); → returns the correct value.
var connectionStringTry = await connectionStringResolver.ResolveAsync(); → returns the correct tenant connection string.
var isMultiTenant = EntityHelper.IsMultiTenant(typeof(xxxx)); → returns true.
var connectionString = dbContext.Database.GetConnectionString(); → returns the host database connection string, but it should be the tenant’s connection string.
What am I missing here? Could you please help me understand why this is happening?
var tenantStore = base.LazyServiceProvider
.GetRequiredService<ITenantStore>();
// as expected
var tenantConfig = await tenantStore.FindAsync(dbContext.CurrentTenant.Id.Value);
// as expected, type is MultiTenantConnectionStringResolver
var connectionStringResolver =
base.LazyServiceProvider.GetRequiredService<IConnectionStringResolver>();
// as expected -> default
var connectionStringName = ConnectionStringNameAttribute.GetConnStringName(typeof(xxxContext));
// as expected
var connectionStringTry = await connectionStringResolver.ResolveAsync();
// as expected -> true
var ismultiTenant = EntityHelper.IsMultiTenant(typeof(xx));
// host db conenction
var connectionString = dbContext.Database.GetConnectionString();
// wrong response because its runing on host db
// First, find the xxx (tenant filter is automatically applied here )
var _ = await dbContext.Set<xxx>()
.AsNoTracking()
.Where(x => x.Code == xxx)
.SingleAsync();
10 Answer(s)
-
0
- Solution The symptoms indicate that your DbContext is being created on the Host side (or before the tenant context is applied), so EF Core is opening the host connection even though MultiTenantConnectionStringResolver can resolve the tenant’s connection string. In ABP, the actual connection used by DbContext is determined when the DbContext is constructed and its options are configured. If the tenant context isn’t active at that moment, the host connection is cached/opened and reused.
Check and fix the following:
- Ensure UseMultiTenancy middleware order In all web/API hosts, the multi-tenancy middleware must run after authentication so the current tenant is available to downstream services resolving connection strings.
app.UseRouting(); app.UseAuthentication(); app.UseMultiTenancy(); app.UseAuthorization();Wrong order (multi-tenancy before authentication) leads to missing tenant context during DbContext creation and thus host DB usage.
- Resolve DbContext inside the tenant scope If you manually resolve repositories/DbContexts or run background jobs, ensure they execute inside the current tenant scope:
using (_currentTenant.Change(tenantId)) { // Resolve repositories/DbContexts here, after tenant has been set var ctx = await repository.GetDbContextAsync(); // Now ctx.Database.GetConnectionString() should be the tenant’s connection string }Do not cache DbContext, DbContextOptions, or services that depend on tenant context across tenant boundaries.
- Do not override DbContext options with a static connection In your module or DbContext factory, avoid code that hardwires a connection string (e.g., options.UseSqlServer(hostConnString)). ABP’s EfCore integration configures the provider and defers the connection string to the IConnectionStringResolver at runtime. If you must configure provider:
Configure<AbpDbContextOptions>(options => { options.UseSqlServer(); // no literal connection string here });If you pass a literal connection string in UseSqlServer, it will always use that (host) connection, bypassing the resolver.
- Use the correct ConnectionStringName Ensure your DbContext has the ConnectionStringName attribute matching the name you configured per tenant (Default unless you changed it):
[ConnectionStringName("Default")] public class MyDbContext : AbpDbContext<MyDbContext> { ... }Since your checks show ConnectionStringNameAttribute.GetConnStringName returns the correct name and the resolver returns the tenant string, this is likely fine.
- Verify tenant connection strings exist and are valid Per-tenant connection strings must be stored in the SaaS tenant (or custom tenant store) and not encrypted in a way the resolver can’t use. If you store encrypted strings, replace or decorate IConnectionStringResolver/IConnectionStringChecker accordingly so DbContext gets a plain connection at runtime. Otherwise, EF Core will fail and may fallback to host (or throw). See sample replacement below:
[Dependency(ReplaceServices = true)] public class EncryptedConnectionStringChecker : SqlServerConnectionStringChecker { private readonly IStringEncryptionService _enc; public EncryptedConnectionStringChecker(IStringEncryptionService enc) => _enc = enc; public override Task<AbpConnectionStringCheckResult> CheckAsync(string cs) => base.CheckAsync(_enc.Decrypt(cs)); }Don’t read Database.GetConnectionString too early If you log dbContext.Database.GetConnectionString() before the tenant scope is active or before the provider initializes, you may see host/null. Inspect after resolving DbContext within the tenant scope.
Background workers and integration events For hosted services, background jobs, or event handlers (out of HTTP pipeline), wrap operations with _currentTenant.Change(tenantId) before resolving repositories/DbContexts.
Minimal checklist you can try now:
- Move app.UseMultiTenancy() after app.UseAuthentication() and before app.UseAuthorization() in all hosts.
- Remove any hard-coded connection strings in UseSqlServer/UseNpgsql; use options.UseSqlServer() only.
- Ensure all code that resolves repositories/DbContexts runs inside using (_currentTenant.Change(tenantId)) when operating for a specific tenant (especially outside controllers).
- If you encrypt tenant connection strings, ensure the runtime resolver/checker returns a decrypted string to EF Core.
- Sources
- Multi-tenancy middleware order (must run after authentication)
- https://abp.io/support/questions/9577
- Connection strings fundamentals and resolver behavior
- https://abp.io/docs/latest/framework/fundamentals/connection-strings
- Custom connection string resolver example and per-context override
- https://abp.io/support/questions/453
- Handling encrypted tenant connection strings via checker override
- https://abp.io/support/questions/9507
This response was generated by AI. If it didn’t solve your problem, feel free to post a new answer — our support team will jump in to help you soon.
-
0
hi
Abp framework doesn't override the
Database.GetConnectionString()method.var connectionString = dbContext.Database.GetConnectionString();You shouldn't use it. Please always use
IConnectionStringResolverThanks.
-
0
I’m not directly using that connection string, but the queries I run through the DbContext are executed on the host database. Should I avoid using it as well?
-
0
hi
Can you share the full query code?
Thanks.
-
0
var _ = await dbContext.Set<xxx>() .AsNoTracking() .Where(x => x.Code == xxx) .SingleAsync();
Because it runs on the host database, it can’t find the record.
-
0
Hi
Can you share a minimal project?
liming.ma@volosoft.com
Thanks.
-
0
It’s a bit difficult for me to prepare a minimal reproduction project at the moment. Are there any specific areas or configurations you would recommend me to check?
-
0
Hi
Are you sure current tenant has separate connection string?
Please check the table data in database and clear the Redis.
Thanks.
-
0
Yes, I have a separate connection string for the tenant. I can see it in the TenantConnectionStrings table, and its name is Default. I’ll clear the Redis cache and try again.
-
0
hi
Can you test your case in a new template project? Then you can share it.
Thanks.