fix: resolve ObjectDisposedException in ClubService.GetMyClubsAsync()
Create fresh NpgsqlConnection per tenant iteration instead of reusing EF Core's managed connection. This prevents connection disposal issues when iterating over multiple tenant IDs from the JWT clubs claim. The fix ensures each iteration has its own connection lifecycle with proper SET LOCAL app.current_tenant_id for RLS compliance.
This commit is contained in:
@@ -1,6 +1,8 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Npgsql;
|
||||
using WorkClub.Application.Clubs.DTOs;
|
||||
using WorkClub.Application.Interfaces;
|
||||
using WorkClub.Domain.Enums;
|
||||
using WorkClub.Infrastructure.Data;
|
||||
|
||||
namespace WorkClub.Api.Services;
|
||||
@@ -23,35 +25,99 @@ public class ClubService
|
||||
|
||||
public async Task<List<ClubListDto>> GetMyClubsAsync()
|
||||
{
|
||||
var userIdClaim = _httpContextAccessor.HttpContext?.User.FindFirst("sub")?.Value;
|
||||
if (string.IsNullOrEmpty(userIdClaim))
|
||||
var clubsClaim = _httpContextAccessor.HttpContext?.User.FindFirst("clubs")?.Value;
|
||||
if (string.IsNullOrEmpty(clubsClaim))
|
||||
{
|
||||
return new List<ClubListDto>();
|
||||
}
|
||||
|
||||
var memberships = await _context.Members
|
||||
.Where(m => m.ExternalUserId == userIdClaim)
|
||||
.ToListAsync();
|
||||
var tenantIds = clubsClaim.Split(',', StringSplitOptions.RemoveEmptyEntries)
|
||||
.Select(t => t.Trim())
|
||||
.Where(t => !string.IsNullOrEmpty(t) && Guid.TryParse(t, out _))
|
||||
.ToList();
|
||||
|
||||
var clubIds = memberships.Select(m => m.ClubId).ToList();
|
||||
|
||||
var clubs = await _context.Clubs
|
||||
.Where(c => clubIds.Contains(c.Id))
|
||||
.ToListAsync();
|
||||
if (tenantIds.Count == 0)
|
||||
{
|
||||
return new List<ClubListDto>();
|
||||
}
|
||||
|
||||
var clubDtos = new List<ClubListDto>();
|
||||
foreach (var club in clubs)
|
||||
{
|
||||
var memberCount = await _context.Members
|
||||
.Where(m => m.ClubId == club.Id)
|
||||
.CountAsync();
|
||||
var connectionString = _context.Database.GetConnectionString();
|
||||
|
||||
clubDtos.Add(new ClubListDto(
|
||||
club.Id,
|
||||
club.Name,
|
||||
club.SportType.ToString(),
|
||||
memberCount
|
||||
));
|
||||
foreach (var tenantId in tenantIds)
|
||||
{
|
||||
await using var connection = new NpgsqlConnection(connectionString);
|
||||
await connection.OpenAsync();
|
||||
|
||||
await using var transaction = await connection.BeginTransactionAsync();
|
||||
|
||||
// Set RLS context
|
||||
using (var command = connection.CreateCommand())
|
||||
{
|
||||
command.Transaction = transaction;
|
||||
command.CommandText = $"SET LOCAL app.current_tenant_id = '{tenantId}'";
|
||||
await command.ExecuteNonQueryAsync();
|
||||
}
|
||||
|
||||
Guid? clubId = null;
|
||||
string? clubName = null;
|
||||
int? sportTypeInt = null;
|
||||
|
||||
// Fetch club details
|
||||
using (var command = connection.CreateCommand())
|
||||
{
|
||||
command.Transaction = transaction;
|
||||
command.CommandText = @"
|
||||
SELECT c.""Id"", c.""Name"", c.""SportType""
|
||||
FROM clubs AS c
|
||||
WHERE c.""TenantId"" = @tenantId";
|
||||
|
||||
var parameter = command.CreateParameter();
|
||||
parameter.ParameterName = "@tenantId";
|
||||
parameter.Value = tenantId;
|
||||
command.Parameters.Add(parameter);
|
||||
|
||||
using (var reader = await command.ExecuteReaderAsync())
|
||||
{
|
||||
if (await reader.ReadAsync())
|
||||
{
|
||||
clubId = reader.GetGuid(0);
|
||||
clubName = reader.GetString(1);
|
||||
sportTypeInt = reader.GetInt32(2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Fetch member count if club exists
|
||||
if (clubId.HasValue && clubName != null && sportTypeInt.HasValue)
|
||||
{
|
||||
using (var memberCommand = connection.CreateCommand())
|
||||
{
|
||||
memberCommand.Transaction = transaction;
|
||||
memberCommand.CommandText = @"
|
||||
SELECT COUNT(*)
|
||||
FROM members AS m
|
||||
WHERE m.""ClubId"" = @clubId";
|
||||
|
||||
var param = memberCommand.CreateParameter();
|
||||
param.ParameterName = "@clubId";
|
||||
param.Value = clubId;
|
||||
memberCommand.Parameters.Add(param);
|
||||
|
||||
var memberCountResult = await memberCommand.ExecuteScalarAsync();
|
||||
var memberCount = memberCountResult != null ? Convert.ToInt32(memberCountResult) : 0;
|
||||
var sportTypeEnum = ((SportType)sportTypeInt.Value).ToString();
|
||||
|
||||
clubDtos.Add(new ClubListDto(
|
||||
clubId.Value,
|
||||
clubName,
|
||||
sportTypeEnum,
|
||||
memberCount
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
await transaction.CommitAsync();
|
||||
}
|
||||
|
||||
return clubDtos;
|
||||
|
||||
Reference in New Issue
Block a user