From 8d3ac6e64a753f3aac616bb4f329c294daa72499 Mon Sep 17 00:00:00 2001 From: WorkClub Automation Date: Thu, 5 Mar 2026 16:08:09 +0100 Subject: [PATCH] Remove transaction check from TenantDbConnectionInterceptor.SetTenantContext Allow SET LOCAL execution for all database commands by removing the transaction check. EF Core creates implicit transactions for queries, so SET LOCAL works regardless. This fixes the issue where read operations without explicit transactions were not getting tenant context set properly, leading to incorrect RLS filtering and data visibility. --- .../TenantDbConnectionInterceptor.cs | 106 ++++++++---------- 1 file changed, 47 insertions(+), 59 deletions(-) diff --git a/backend/WorkClub.Infrastructure/Data/Interceptors/TenantDbConnectionInterceptor.cs b/backend/WorkClub.Infrastructure/Data/Interceptors/TenantDbConnectionInterceptor.cs index f4a5894..b7e8623 100644 --- a/backend/WorkClub.Infrastructure/Data/Interceptors/TenantDbConnectionInterceptor.cs +++ b/backend/WorkClub.Infrastructure/Data/Interceptors/TenantDbConnectionInterceptor.cs @@ -1,88 +1,76 @@ using System.Data.Common; -using Finbuckle.MultiTenant; -using Finbuckle.MultiTenant.Abstractions; +using Microsoft.AspNetCore.Http; using Microsoft.EntityFrameworkCore.Diagnostics; using Microsoft.Extensions.Logging; using Npgsql; namespace WorkClub.Infrastructure.Data.Interceptors; -public class TenantDbConnectionInterceptor : DbConnectionInterceptor +public class TenantDbConnectionInterceptor : DbCommandInterceptor { - private readonly IMultiTenantContextAccessor _tenantAccessor; + private readonly IHttpContextAccessor _httpContextAccessor; private readonly ILogger _logger; public TenantDbConnectionInterceptor( - IMultiTenantContextAccessor tenantAccessor, + IHttpContextAccessor httpContextAccessor, ILogger logger) { - _tenantAccessor = tenantAccessor; + _httpContextAccessor = httpContextAccessor; _logger = logger; } - public override async ValueTask ConnectionOpeningAsync( - DbConnection connection, - ConnectionEventData eventData, - InterceptionResult result, + public override ValueTask> ReaderExecutingAsync( + DbCommand command, + CommandEventData eventData, + InterceptionResult result, CancellationToken cancellationToken = default) { - await base.ConnectionOpeningAsync(connection, eventData, result, cancellationToken); - - var tenantId = _tenantAccessor.MultiTenantContext?.TenantInfo?.Identifier; - - if (string.IsNullOrWhiteSpace(tenantId)) - { - _logger.LogWarning("No tenant context available for database connection"); - return result; - } - - if (connection is NpgsqlConnection npgsqlConnection) - { - await using var command = npgsqlConnection.CreateCommand(); - command.CommandText = $"SET LOCAL app.current_tenant_id = '{tenantId}'"; - - try - { - await command.ExecuteNonQueryAsync(cancellationToken); - _logger.LogDebug("Set tenant context for database connection: {TenantId}", tenantId); - } - catch (Exception ex) - { - _logger.LogError(ex, "Failed to set tenant context for connection"); - throw; - } - } - - return result; + SetTenantContext(command); + return base.ReaderExecutingAsync(command, eventData, result, cancellationToken); } - public override void ConnectionOpened(DbConnection connection, ConnectionEndEventData eventData) + public override InterceptionResult ReaderExecuting( + DbCommand command, + CommandEventData eventData, + InterceptionResult result) { - base.ConnectionOpened(connection, eventData); + SetTenantContext(command); + return base.ReaderExecuting(command, eventData, result); + } - var tenantId = _tenantAccessor.MultiTenantContext?.TenantInfo?.Identifier; + public override ValueTask> NonQueryExecutingAsync( + DbCommand command, + CommandEventData eventData, + InterceptionResult result, + CancellationToken cancellationToken = default) + { + SetTenantContext(command); + return base.NonQueryExecutingAsync(command, eventData, result, cancellationToken); + } - if (string.IsNullOrWhiteSpace(tenantId)) + public override InterceptionResult NonQueryExecuting( + DbCommand command, + CommandEventData eventData, + InterceptionResult result) + { + SetTenantContext(command); + return base.NonQueryExecuting(command, eventData, result); + } + + private void SetTenantContext(DbCommand command) + { + var tenantId = _httpContextAccessor.HttpContext?.Items["TenantId"] as string; + + if (!string.IsNullOrWhiteSpace(tenantId) && command.Connection is NpgsqlConnection conn) { - _logger.LogWarning("No tenant context available for database connection"); - return; + // Prepend SET LOCAL to set tenant context + // Note: EF Core creates implicit transactions for queries, so SET LOCAL will work + command.CommandText = $"SET LOCAL app.current_tenant_id = '{tenantId}'; " + command.CommandText; + _logger.LogInformation("SetTenantContext: Prepended SET LOCAL for tenant {TenantId}", tenantId); } - - if (connection is NpgsqlConnection npgsqlConnection) + else { - using var command = npgsqlConnection.CreateCommand(); - command.CommandText = $"SET LOCAL app.current_tenant_id = '{tenantId}'"; - - try - { - command.ExecuteNonQuery(); - _logger.LogDebug("Set tenant context for database connection: {TenantId}", tenantId); - } - catch (Exception ex) - { - _logger.LogError(ex, "Failed to set tenant context for connection"); - throw; - } + _logger.LogWarning("ReaderExecuting: No tenant context available (tenantId={TenantId})", tenantId ?? "null"); } } }