Files
Core_Be/Infra/Tenancy/TenantSchemaIterator.cs
T
Dhananjaya99 bffc9b1686 first commit
2026-09-28 11:44:26 +05:30

100 lines
4.4 KiB
C#

using Microsoft.EntityFrameworkCore;
namespace Fitness_core_BE.Infra.Tenancy
{
// A matched Central/Tenant context pair bound to one specific, already-known schema --
// built for callers that have no HttpContext to resolve a tenant from (background jobs),
// so TenantConnectionResolver's request-based resolution can't apply. Dispose both
// contexts together via `await using`.
public sealed class TenantSchemaContext : IAsyncDisposable
{
public Guid ClientId { get; }
public string SchemaName { get; }
public CentralDbContext Central { get; }
public TenantDbContext Tenant { get; }
public TenantSchemaContext(Guid clientId, string schemaName, CentralDbContext central, TenantDbContext tenant)
{
ClientId = clientId;
SchemaName = schemaName;
Central = central;
Tenant = tenant;
}
public async ValueTask DisposeAsync()
{
await Tenant.DisposeAsync();
await Central.DisposeAsync();
}
}
// Single club, single database -- every background job that used to loop over "every
// provisioned tenant schema" now just runs once against the one Client row / one DB. Kept
// as a class (rather than deleting it and rewriting every caller) so OverdueLockJob,
// SubscriptionRenewalJob and AdminBackgroundJobsController keep working unchanged.
public class TenantSchemaIterator
{
private readonly IConfiguration _configuration;
public TenantSchemaIterator(IConfiguration configuration)
{
_configuration = configuration;
}
// Every client row (there's exactly one for Vijaya) -- callers open their own
// TenantSchemaContext per entry.
public async IAsyncEnumerable<(Guid ClientId, string SchemaName)> GetProvisionedRegistrationsAsync(
[System.Runtime.CompilerServices.EnumeratorCancellation] CancellationToken ct = default)
{
List<Guid> clientIds;
await using (var central = CreateCentralDbContext())
{
clientIds = await central.Client.AsNoTracking().Select(c => c.ClientId).ToListAsync(ct);
}
foreach (var clientId in clientIds)
yield return (clientId, "default");
}
// One client's schema, when the caller already knows the ClientId (e.g. OverdueLockJob's
// existing per-client loop from the central Client table). Null if the client doesn't exist.
public async Task<TenantSchemaContext?> OpenSchemaForClientAsync(Guid clientId, CancellationToken ct = default)
{
bool exists;
await using (var central = CreateCentralDbContext())
{
exists = await central.Client.AsNoTracking().AnyAsync(c => c.ClientId == clientId, ct);
}
return exists ? OpenSchema(clientId, "default") : null;
}
// Opens a matched context pair for an already-known ClientId. Building the contexts
// doesn't itself open a connection -- EF Core connects lazily on first query.
public TenantSchemaContext OpenSchema(Guid clientId, string schemaName)
{
var central = CreateCentralDbContext();
var tenant = CreateTenantDbContext();
return new TenantSchemaContext(clientId, schemaName, central, tenant);
}
private CentralDbContext CreateCentralDbContext()
{
var connectionString = _configuration.GetConnectionString("DefaultConnection")
?? throw new InvalidOperationException("DefaultConnection is not configured.");
var optionsBuilder = new DbContextOptionsBuilder<CentralDbContext>();
optionsBuilder.UseMySql(connectionString, new MySqlServerVersion(new Version(8, 0, 0)));
return new CentralDbContext(optionsBuilder.Options);
}
private TenantDbContext CreateTenantDbContext()
{
var connectionString = _configuration.GetConnectionString("DefaultConnection")
?? throw new InvalidOperationException("DefaultConnection is not configured.");
var optionsBuilder = new DbContextOptionsBuilder<TenantDbContext>();
optionsBuilder.UseMySql(connectionString, new MySqlServerVersion(new Version(8, 0, 0)));
return new TenantDbContext(optionsBuilder.Options);
}
}
}