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 IDbContextTransaction? _tx; private ITenantContext? _tenant; public string Backend => "ef"; public EFUnitOfWork( IDbScope scope, ITenantDbContextFactory factory, IConnectionMultiplexer? redis = null) { _scope = scope; _factory = factory; _redis = redis; } public async Task BeginAsync(ITenantContext tenant, IsolationLevel isolation = IsolationLevel.ReadCommitted, CancellationToken ct = default) { if (_tx is not null) return; _tenant = tenant ?? throw new ArgumentNullException(nameof(tenant)); _scope.EnsureForTenant(tenant); var db = _scope.Get(); _tx = await db.Database.BeginTransactionAsync(isolation, ct); } public async Task CommitAsync(CancellationToken ct = default) { if (_tx is null) return; var db = _scope.Get(); 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); await _tx.CommitAsync(ct); 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)); 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 { } } 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 { } _tx = null; _tenant = null; return ValueTask.CompletedTask; } }