I have a multi-tenant PostgreSQL database which uses row-level security to control what a tenant should be able to see.
I'm using EF Core with ASP.NET Core to access this database. To handle tenant access, I'm using a connection interceptor to run the appropriate database commands. For example:
To do this, I've created a connection interceptor.
public override async Task ConnectionOpenedAsync(
DbConnection connection,
ConnectionEndEventData eventData,
CancellationToken cancellationToken = default)
{
using var command = connection.CreateCommand();
var commandText = string.Join("\r\n",
"SET SESSION ROLE TO tenant;",
$"SET SESSION tenant.userId TO '{this.userId}';");
command.CommandText = commandText;
await command.ExecuteNonQueryAsync(cancellationToken);
await base.ConnectionOpenedAsync(connection, eventData, cancellationToken);
}
All of this works fine, but I also need to access the database in a non-tenant context. In other words, I don't want the above interceptor to always run.
I'm trying to find the best way to do this. My initial attempt was to use interfaces to define the tenant or 'global' interface
// (MyDbContext implements both IMyTenantDbContext and IMyGlobalDbContext)
services.AddDbContext<IMyTenantDbContext, MyDbContext>((sp, options) =>
{
var tenantInterceptor = sp.GetRequiredService<SetTenantConnectionInterceptor>();
options
.UseNpgsql(connectionString)
.AddInterceptors(tenantInterceptor);
});
services.AddDbContext<IMyGlobalDbContext, MyDbContext>(options =>
{
options
.UseNpgsql(connectionString);
});
However for some reason the DI system uses the first AddDbContext, even if I try to inject a IMyGlobalDbContext meaning the interceptor is still being used.
I'm sure there must be a better way of doing this, so please let me know if I'm doing it all completely wrong.