Files
work-club-manager/backend/WorkClub.Infrastructure/Data/Interceptors/TenantDbConnectionInterceptor.cs
WorkClub Automation 8d3ac6e64a 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.
2026-03-05 16:08:09 +01:00

77 lines
2.7 KiB
C#

using System.Data.Common;
using Microsoft.AspNetCore.Http;
using Microsoft.EntityFrameworkCore.Diagnostics;
using Microsoft.Extensions.Logging;
using Npgsql;
namespace WorkClub.Infrastructure.Data.Interceptors;
public class TenantDbConnectionInterceptor : DbCommandInterceptor
{
private readonly IHttpContextAccessor _httpContextAccessor;
private readonly ILogger<TenantDbConnectionInterceptor> _logger;
public TenantDbConnectionInterceptor(
IHttpContextAccessor httpContextAccessor,
ILogger<TenantDbConnectionInterceptor> logger)
{
_httpContextAccessor = httpContextAccessor;
_logger = logger;
}
public override ValueTask<InterceptionResult<DbDataReader>> ReaderExecutingAsync(
DbCommand command,
CommandEventData eventData,
InterceptionResult<DbDataReader> result,
CancellationToken cancellationToken = default)
{
SetTenantContext(command);
return base.ReaderExecutingAsync(command, eventData, result, cancellationToken);
}
public override InterceptionResult<DbDataReader> ReaderExecuting(
DbCommand command,
CommandEventData eventData,
InterceptionResult<DbDataReader> result)
{
SetTenantContext(command);
return base.ReaderExecuting(command, eventData, result);
}
public override ValueTask<InterceptionResult<int>> NonQueryExecutingAsync(
DbCommand command,
CommandEventData eventData,
InterceptionResult<int> result,
CancellationToken cancellationToken = default)
{
SetTenantContext(command);
return base.NonQueryExecutingAsync(command, eventData, result, cancellationToken);
}
public override InterceptionResult<int> NonQueryExecuting(
DbCommand command,
CommandEventData eventData,
InterceptionResult<int> 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)
{
// 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);
}
else
{
_logger.LogWarning("ReaderExecuting: No tenant context available (tenantId={TenantId})", tenantId ?? "null");
}
}
}