using PostFundManagement.Domain.Abstractions; using PostFundManagement.Domain.Entities; namespace PostFundManagement.Infrastructure.Database; public sealed class ApplicationDbContext(DbContextOptions options, ITenantContextService tenantService) : DbContext(options) { private bool BypassFilter => tenantService.IsAdmin; private long CurrentTenantId => tenantService.TenantId; public DbSet TenantUsers => Set(); public DbSet TenantOrganisations => Set(); public DbSet Tenants => Set(); public DbSet Notifications => Set(); public DbSet Invites => Set(); public DbSet Activities => Set(); public DbSet AuditLogs => Set(); public DbSet SiteVisits => Set(); public DbSet ChangeRequests => Set(); public DbSet ComplianceItems => Set(); public DbSet Issues => Set(); public DbSet Outcomes => Set(); public DbSet Beneficiaries => Set(); public DbSet Evidences => Set(); public DbSet Risks => Set(); public DbSet Indicators => Set(); public DbSet Disbursements => Set(); public DbSet Milestones => Set(); public DbSet Budgets => Set(); public DbSet Contracts => Set(); public DbSet Awards => Set(); public DbSet Projects => Set(); public DbSet Organisations => Set(); public DbSet Rules => Set(); public DbSet Programmes => Set(); public DbSet Portfolios => Set(); public DbSet Instruments => Set(); public DbSet Users => Set(); private void ApplyGlobalTenantFilter(ModelBuilder modelBuilder) where TEntity : class, ITenantEntity => modelBuilder.Entity().HasQueryFilter(e => BypassFilter || e.TenantId == CurrentTenantId); protected override void OnModelCreating(ModelBuilder modelBuilder) { base.OnModelCreating(modelBuilder); foreach (var entityType in modelBuilder.Model.GetEntityTypes()) { if (typeof(ITenantEntity).IsAssignableFrom(entityType.ClrType)) { var method = typeof(ApplicationDbContext).GetMethod(nameof(ApplyGlobalTenantFilter), BindingFlags.NonPublic | BindingFlags.Instance)?.MakeGenericMethod(entityType.ClrType); method?.Invoke(this, [modelBuilder]); } } modelBuilder.Entity().HasQueryFilter(o => BypassFilter || Set().Any(to => to.TenantId == CurrentTenantId && to.OrganisationId == o.Id)); modelBuilder.Entity() .HasQueryFilter(to => BypassFilter || to.TenantId == CurrentTenantId); modelBuilder.Entity() .HasQueryFilter(to => BypassFilter || to.TenantId == CurrentTenantId); } public override Task SaveChangesAsync(CancellationToken cancellationToken = default) { AlterQualifyingTenantEntity(); return base.SaveChangesAsync(cancellationToken); } public override int SaveChanges() { AlterQualifyingTenantEntity(); return base.SaveChanges(); } private void AlterQualifyingTenantEntity() { var entries = ChangeTracker.Entries() .Where(e => e.State == EntityState.Added && e.Entity.TenantId == 0) .ToList(); if (entries.Count == 0) return; if (CurrentTenantId <= 0 && !BypassFilter) throw new InvalidOperationException("Cannot save multi-tenant entity: CurrentTenantId is not set in the active execution context."); foreach (var entry in entries) entry.Entity.TenantId = CurrentTenantId; } }