namespace WalletService.Infrastructure;
using MediatR;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Storage;
using WalletService.Domain.AggregatesModel.PaymentAggregate;
using WalletService.Domain.AggregatesModel.PointAccountAggregate;
using WalletService.Domain.AggregatesModel.WalletAggregate;
using WalletService.Domain.SeedWork;
///
/// EN: Tenant provider interface for WalletServiceContext global query filters.
/// Wallet service uses user_id as primary tenant key (wallets are per-user).
/// VI: Interface tenant provider cho global query filters của WalletServiceContext.
/// Wallet service sử dụng user_id làm tenant key chính (ví là per-user).
///
public interface IWalletTenantProvider
{
Guid? GetCurrentUserId();
bool ShouldBypassTenantFilter();
}
///
/// EN: Database context for Wallet Service with Unit of Work pattern and multi-tenant filtering.
/// VI: Database context cho Wallet Service với pattern Unit of Work và filtering đa tenant.
///
public class WalletServiceContext : DbContext, IUnitOfWork
{
private readonly IMediator _mediator;
private readonly IWalletTenantProvider? _tenantProvider;
private IDbContextTransaction? _currentTransaction;
public DbSet Wallets { get; set; } = null!;
public DbSet WalletItems { get; set; } = null!;
public DbSet WalletTransactions { get; set; } = null!;
public DbSet WalletHolds { get; set; } = null!;
public DbSet Payments { get; set; } = null!;
public DbSet PointAccounts { get; set; } = null!;
public DbSet PointTransactions { get; set; } = null!;
public bool HasActiveTransaction => _currentTransaction != null;
public WalletServiceContext(DbContextOptions options, IMediator mediator)
: base(options)
{
_mediator = mediator ?? throw new ArgumentNullException(nameof(mediator));
}
///
/// EN: Constructor with tenant provider for multi-tenant filtering.
/// VI: Constructor với tenant provider cho filtering đa tenant.
///
public WalletServiceContext(
DbContextOptions options,
IMediator mediator,
IWalletTenantProvider tenantProvider)
: base(options)
{
_mediator = mediator ?? throw new ArgumentNullException(nameof(mediator));
_tenantProvider = tenantProvider;
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
// EN: Apply all entity configurations
// VI: Áp dụng tất cả các cấu hình entity
modelBuilder.ApplyConfigurationsFromAssembly(typeof(WalletServiceContext).Assembly);
// EN: Global query filter for tenant isolation on Wallets (user-level).
// Wallets belong to a user, not a shop. Each user can only see their own wallet.
// VI: Global query filter cho cách ly tenant trên Wallets (cấp user).
// Wallets thuộc về user, không phải shop. Mỗi user chỉ thấy ví của mình.
modelBuilder.Entity().HasQueryFilter(w =>
_tenantProvider == null
|| _tenantProvider.ShouldBypassTenantFilter()
|| _tenantProvider.GetCurrentUserId() == null
|| w.UserId == _tenantProvider.GetCurrentUserId());
}
///
/// EN: Save changes and dispatch domain events
/// VI: Lưu thay đổi và dispatch domain events
///
public async Task SaveEntitiesAsync(CancellationToken cancellationToken = default)
{
// EN: Dispatch domain events after saving
// VI: Dispatch domain events sau khi lưu
await DispatchDomainEventsAsync();
// EN: Save changes to database
// VI: Lưu thay đổi vào database
var result = await base.SaveChangesAsync(cancellationToken);
return result > 0;
}
///
/// EN: Begin a new transaction
/// VI: Bắt đầu transaction mới
///
public async Task BeginTransactionAsync()
{
if (_currentTransaction != null) return null;
_currentTransaction = await Database.BeginTransactionAsync();
return _currentTransaction;
}
///
/// EN: Commit the current transaction
/// VI: Commit transaction hiện tại
///
public async Task CommitTransactionAsync(IDbContextTransaction transaction)
{
if (transaction == null) throw new ArgumentNullException(nameof(transaction));
if (transaction != _currentTransaction)
throw new InvalidOperationException($"Transaction {transaction.TransactionId} is not current");
try
{
await SaveChangesAsync();
await transaction.CommitAsync();
}
catch
{
RollbackTransaction();
throw;
}
finally
{
if (_currentTransaction != null)
{
_currentTransaction.Dispose();
_currentTransaction = null;
}
}
}
///
/// EN: Rollback the current transaction
/// VI: Rollback transaction hiện tại
///
public void RollbackTransaction()
{
try
{
_currentTransaction?.Rollback();
}
finally
{
if (_currentTransaction != null)
{
_currentTransaction.Dispose();
_currentTransaction = null;
}
}
}
private async Task DispatchDomainEventsAsync()
{
var domainEntities = ChangeTracker
.Entries()
.Where(x => x.Entity.DomainEvents.Any())
.ToList();
var domainEvents = domainEntities
.SelectMany(x => x.Entity.DomainEvents)
.ToList();
domainEntities.ForEach(entity => entity.Entity.ClearDomainEvents());
foreach (var domainEvent in domainEvents)
{
await _mediator.Publish(domainEvent);
}
}
}