using System.Collections.Concurrent; using AMREZ.EOP.Abstractions.Applications.Tenancy; using AMREZ.EOP.Abstractions.Storage; using AMREZ.EOP.Infrastructures.Data; using AMREZ.EOP.Infrastructures.Options; using Microsoft.Extensions.Options; using StackExchange.Redis; using IDatabase = Microsoft.EntityFrameworkCore.Storage.IDatabase; namespace AMREZ.EOP.Infrastructures.Storage; public sealed class DbScope : IDbScope, IAsyncDisposable { private readonly ITenantDbContextFactory _factory; private readonly IOptions _opts; private readonly IConnectionMultiplexer? _redis; private readonly ConcurrentDictionary _cache = new(); public DbScope(ITenantDbContextFactory factory, IOptions opts, IConnectionMultiplexer? redis = null) { _factory = factory; _opts = opts; _redis = redis; } private ITenantContext? _tenant; public void EnsureForTenant(ITenantContext tenant) { if (_tenant?.Id == tenant.Id) return; _tenant = tenant; _cache.Clear(); var ef = _factory.Create(tenant); _cache[typeof(AppDbContext)] = ef; if (_redis != null) _cache[typeof(IDatabase)] = _redis.GetDatabase(_opts.Value.RedisDb); } public TContext Get() where TContext : class { if (_tenant is null) throw new InvalidOperationException("Tenant not set. Call EnsureForTenant first."); if (_cache.TryGetValue(typeof(TContext), out var obj)) return (TContext)obj; if (typeof(TContext) == typeof(AppDbContext)) { var ef = _factory.Create(_tenant); _cache[typeof(AppDbContext)] = ef; return (TContext)(object)ef; } if (typeof(TContext) == typeof(IDatabase)) { if (_redis is null) throw new InvalidOperationException("Redis not configured"); var db = _redis.GetDatabase(_opts.Value.RedisDb); _cache[typeof(IDatabase)] = db; return (TContext)(object)db; } throw new NotSupportedException($"Unknown context type {typeof(TContext).Name}"); } public async ValueTask DisposeAsync() { if (_cache.TryGetValue(typeof(AppDbContext), out var ef) && ef is AppDbContext db) await db.DisposeAsync(); _cache.Clear(); } }