Init Git
This commit is contained in:
119
AMREZ.EOP.Infrastructures/UnitOfWork/EFUnitOfWork.cs
Normal file
119
AMREZ.EOP.Infrastructures/UnitOfWork/EFUnitOfWork.cs
Normal file
@@ -0,0 +1,119 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
37
AMREZ.EOP.Infrastructures/UnitOfWork/RedisUnitOfWork.cs
Normal file
37
AMREZ.EOP.Infrastructures/UnitOfWork/RedisUnitOfWork.cs
Normal file
@@ -0,0 +1,37 @@
|
||||
using System.Data;
|
||||
using AMREZ.EOP.Abstractions.Applications.Tenancy;
|
||||
using AMREZ.EOP.Abstractions.Infrastructures.Common;
|
||||
using AMREZ.EOP.Abstractions.Storage;
|
||||
using StackExchange.Redis;
|
||||
|
||||
namespace AMREZ.EOP.Infrastructures.UnitOfWork;
|
||||
|
||||
public sealed class RedisUnitOfWork : IUnitOfWork
|
||||
{
|
||||
private readonly IDbScope _scope;
|
||||
private ITransaction? _tx;
|
||||
|
||||
public RedisUnitOfWork(IDbScope scope) => _scope = scope;
|
||||
public string Backend => "redis";
|
||||
|
||||
public Task BeginAsync(ITenantContext tenant, IsolationLevel isolation = IsolationLevel.ReadCommitted, CancellationToken ct = default)
|
||||
{
|
||||
_scope.EnsureForTenant(tenant);
|
||||
var db = _scope.Get<IDatabase>();
|
||||
_tx = db.CreateTransaction();
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public async Task CommitAsync(CancellationToken ct = default)
|
||||
{
|
||||
if (_tx != null) await _tx.ExecuteAsync();
|
||||
}
|
||||
|
||||
public Task RollbackAsync(CancellationToken ct = default)
|
||||
{
|
||||
_tx = null; // drop
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public ValueTask DisposeAsync() => ValueTask.CompletedTask;
|
||||
}
|
||||
Reference in New Issue
Block a user