using System.Data; using System.Text.Json; using AMREZ.EOP.Abstractions.Applications.Tenancy; using AMREZ.EOP.Abstractions.Infrastructures.Common; using AMREZ.EOP.Abstractions.Storage; using AMREZ.EOP.Domain.Entities.Authentications; using AMREZ.EOP.Domain.Shared._Users; using AMREZ.EOP.Infrastructures.Data; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Storage; using StackExchange.Redis; namespace AMREZ.EOP.Infrastructures.UnitOfWork; public sealed class EFUnitOfWork : IUnitOfWork, IAsyncDisposable { private readonly IDbScope _scope; private readonly ITenantDbContextFactory _factory; private readonly IConnectionMultiplexer? _redis; private AppDbContext? _db; private IDbContextTransaction? _tx; private ITenantContext? _tenant; public string Backend => "ef"; public EFUnitOfWork( IDbScope scope, ITenantDbContextFactory factory, IConnectionMultiplexer? redis = null) { _scope = scope; _factory = factory; _redis = redis; // optional; null ได้ } public async Task BeginAsync(ITenantContext tenant, IsolationLevel isolation = IsolationLevel.ReadCommitted, CancellationToken ct = default) { if (_db is not null) return; _tenant = tenant ?? throw new ArgumentNullException(nameof(tenant)); _scope.EnsureForTenant(tenant); _db = _scope.Get(); _tx = await _db.Database.BeginTransactionAsync(isolation, ct); } public async Task CommitAsync(CancellationToken ct = default) { if (_db is null) return; // ยังไม่ Begin // track entities ที่เปลี่ยน (เพื่อ invalidate cache แบบแม่นขึ้น) var changedUsers = _db.ChangeTracker.Entries() .Where(e => e.State is EntityState.Added or EntityState.Modified or EntityState.Deleted) .Select(e => e.Entity) .ToList(); var changedIdentities = _db.ChangeTracker.Entries() .Where(e => e.State is EntityState.Added or EntityState.Modified or EntityState.Deleted) .Select(e => e.Entity) .ToList(); await _db.SaveChangesAsync(ct); if (_tx is not null) await _tx.CommitAsync(ct); // optional: invalidate/refresh Redis (ล่มก็ไม่พัง UoW) if (_redis is not null && _tenant is not null && (changedUsers.Count > 0 || changedIdentities.Count > 0)) { var r = _redis.GetDatabase(); var tenantId = _tenant.Id; var tasks = new List(); foreach (var u in changedUsers) { var keyId = $"eop:{tenantId}:user:id:{u.Id:N}"; tasks.Add(r.KeyDeleteAsync(keyId)); // refresh cache (ถ้าต้องการ) var payload = JsonSerializer.Serialize(u); var ttl = TimeSpan.FromMinutes(5); tasks.Add(r.StringSetAsync(keyId, payload, ttl)); } foreach (var i in changedIdentities.Where(i => i.Type == IdentityType.Email)) { var k = $"eop:{tenantId}:user:identity:email:{i.Identifier}"; tasks.Add(r.KeyDeleteAsync(k)); } try { await Task.WhenAll(tasks); } catch { /* swallow */ } } await DisposeAsync(); } public async Task RollbackAsync(CancellationToken ct = default) { try { if (_tx is not null) await _tx.RollbackAsync(ct); } finally { await DisposeAsync(); } } public ValueTask DisposeAsync() { try { _tx?.Dispose(); } catch { /* ignore */ } try { _db?.Dispose(); } catch { /* ignore */ } _tx = null; _db = null; _tenant = null; return ValueTask.CompletedTask; } }