100 lines
4.4 KiB
C#
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);
|
|
}
|
|
}
|
|
}
|