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.
This commit is contained in:
@@ -1,88 +1,76 @@
|
|||||||
using System.Data.Common;
|
using System.Data.Common;
|
||||||
using Finbuckle.MultiTenant;
|
using Microsoft.AspNetCore.Http;
|
||||||
using Finbuckle.MultiTenant.Abstractions;
|
|
||||||
using Microsoft.EntityFrameworkCore.Diagnostics;
|
using Microsoft.EntityFrameworkCore.Diagnostics;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using Npgsql;
|
using Npgsql;
|
||||||
|
|
||||||
namespace WorkClub.Infrastructure.Data.Interceptors;
|
namespace WorkClub.Infrastructure.Data.Interceptors;
|
||||||
|
|
||||||
public class TenantDbConnectionInterceptor : DbConnectionInterceptor
|
public class TenantDbConnectionInterceptor : DbCommandInterceptor
|
||||||
{
|
{
|
||||||
private readonly IMultiTenantContextAccessor _tenantAccessor;
|
private readonly IHttpContextAccessor _httpContextAccessor;
|
||||||
private readonly ILogger<TenantDbConnectionInterceptor> _logger;
|
private readonly ILogger<TenantDbConnectionInterceptor> _logger;
|
||||||
|
|
||||||
public TenantDbConnectionInterceptor(
|
public TenantDbConnectionInterceptor(
|
||||||
IMultiTenantContextAccessor tenantAccessor,
|
IHttpContextAccessor httpContextAccessor,
|
||||||
ILogger<TenantDbConnectionInterceptor> logger)
|
ILogger<TenantDbConnectionInterceptor> logger)
|
||||||
{
|
{
|
||||||
_tenantAccessor = tenantAccessor;
|
_httpContextAccessor = httpContextAccessor;
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override async ValueTask<InterceptionResult> ConnectionOpeningAsync(
|
public override ValueTask<InterceptionResult<DbDataReader>> ReaderExecutingAsync(
|
||||||
DbConnection connection,
|
DbCommand command,
|
||||||
ConnectionEventData eventData,
|
CommandEventData eventData,
|
||||||
InterceptionResult result,
|
InterceptionResult<DbDataReader> result,
|
||||||
CancellationToken cancellationToken = default)
|
CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
await base.ConnectionOpeningAsync(connection, eventData, result, cancellationToken);
|
SetTenantContext(command);
|
||||||
|
return base.ReaderExecutingAsync(command, eventData, result, cancellationToken);
|
||||||
|
}
|
||||||
|
|
||||||
var tenantId = _tenantAccessor.MultiTenantContext?.TenantInfo?.Identifier;
|
public override InterceptionResult<DbDataReader> ReaderExecuting(
|
||||||
|
DbCommand command,
|
||||||
if (string.IsNullOrWhiteSpace(tenantId))
|
CommandEventData eventData,
|
||||||
|
InterceptionResult<DbDataReader> result)
|
||||||
{
|
{
|
||||||
_logger.LogWarning("No tenant context available for database connection");
|
SetTenantContext(command);
|
||||||
return result;
|
return base.ReaderExecuting(command, eventData, result);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (connection is NpgsqlConnection npgsqlConnection)
|
public override ValueTask<InterceptionResult<int>> NonQueryExecutingAsync(
|
||||||
|
DbCommand command,
|
||||||
|
CommandEventData eventData,
|
||||||
|
InterceptionResult<int> result,
|
||||||
|
CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
await using var command = npgsqlConnection.CreateCommand();
|
SetTenantContext(command);
|
||||||
command.CommandText = $"SET LOCAL app.current_tenant_id = '{tenantId}'";
|
return base.NonQueryExecutingAsync(command, eventData, result, cancellationToken);
|
||||||
|
|
||||||
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;
|
public override InterceptionResult<int> NonQueryExecuting(
|
||||||
|
DbCommand command,
|
||||||
|
CommandEventData eventData,
|
||||||
|
InterceptionResult<int> result)
|
||||||
|
{
|
||||||
|
SetTenantContext(command);
|
||||||
|
return base.NonQueryExecuting(command, eventData, result);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void ConnectionOpened(DbConnection connection, ConnectionEndEventData eventData)
|
private void SetTenantContext(DbCommand command)
|
||||||
{
|
{
|
||||||
base.ConnectionOpened(connection, eventData);
|
var tenantId = _httpContextAccessor.HttpContext?.Items["TenantId"] as string;
|
||||||
|
|
||||||
var tenantId = _tenantAccessor.MultiTenantContext?.TenantInfo?.Identifier;
|
if (!string.IsNullOrWhiteSpace(tenantId) && command.Connection is NpgsqlConnection conn)
|
||||||
|
|
||||||
if (string.IsNullOrWhiteSpace(tenantId))
|
|
||||||
{
|
{
|
||||||
_logger.LogWarning("No tenant context available for database connection");
|
// Prepend SET LOCAL to set tenant context
|
||||||
return;
|
// 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
|
||||||
if (connection is NpgsqlConnection npgsqlConnection)
|
|
||||||
{
|
{
|
||||||
using var command = npgsqlConnection.CreateCommand();
|
_logger.LogWarning("ReaderExecuting: No tenant context available (tenantId={TenantId})", tenantId ?? "null");
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user