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); } } }