Files
amrez-nova-eop-services-api/AMREZ.EOP.Infrastructures/UnitOfWork/EFUnitOfWork.cs
Thanakarn Klangkasame 92e614674c Init Git
2025-09-30 11:01:02 +07:00

119 lines
3.9 KiB
C#

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<AppDbContext>();
_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<User>()
.Where(e => e.State is EntityState.Added or EntityState.Modified or EntityState.Deleted)
.Select(e => e.Entity)
.ToList();
var changedIdentities = _db.ChangeTracker.Entries<UserIdentity>()
.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<Task>();
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;
}
}