using Microsoft.EntityFrameworkCore; using DiunaBI.Domain.Entities; namespace DiunaBI.Infrastructure.Data; public class AppDbContext(DbContextOptions options) : DbContext(options) { public DbSet Users { get; init; } public DbSet Layers { get; init; } public DbSet Records { get; init; } public DbSet RecordHistory { get; init; } public DbSet ProcessSources { get; init; } public DbSet DataInbox { get; init; } public DbSet QueueJobs { get; init; } protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Entity().HasKey(x => x.Id); modelBuilder.Entity().Property(x => x.Email).HasMaxLength(50); modelBuilder.Entity().Property(x => x.UserName).HasMaxLength(50); modelBuilder.Entity().HasKey(x => x.Id); modelBuilder.Entity().Property(x => x.Number).IsRequired(); modelBuilder.Entity().Property(x => x.Name).IsRequired().HasMaxLength(50); modelBuilder.Entity().Property(x => x.Type).IsRequired().HasConversion(); modelBuilder.Entity().Property(x => x.CreatedAt).IsRequired(); modelBuilder.Entity().Property(x => x.ModifiedAt).IsRequired(); modelBuilder.Entity().Property(x => x.IsDeleted).IsRequired().HasDefaultValue(false); modelBuilder.Entity().Property(x => x.IsCancelled).IsRequired().HasDefaultValue(false); modelBuilder.Entity().Property(x => x.CreatedById).IsRequired(); modelBuilder.Entity().Property(x => x.ModifiedById).IsRequired(); modelBuilder.Entity() .HasOne(x => x.CreatedBy) .WithMany() .HasForeignKey(x => x.CreatedById) .OnDelete(DeleteBehavior.Restrict); modelBuilder.Entity() .HasOne(x => x.ModifiedBy) .WithMany() .HasForeignKey(x => x.ModifiedById) .OnDelete(DeleteBehavior.Restrict); modelBuilder.Entity() .HasMany(x => x.Records) .WithOne() .HasForeignKey(r => r.LayerId) .OnDelete(DeleteBehavior.Cascade); modelBuilder.Entity().HasKey(x => x.Id); modelBuilder.Entity().Property(x => x.Code).IsRequired().HasMaxLength(50); modelBuilder.Entity().Property(x => x.Desc1).HasMaxLength(10000); modelBuilder.Entity().Property(x => x.CreatedAt); modelBuilder.Entity().Property(x => x.ModifiedAt); modelBuilder.Entity().Property(x => x.IsDeleted); modelBuilder.Entity().Property(x => x.CreatedById).IsRequired(); modelBuilder.Entity().Property(x => x.ModifiedById).IsRequired(); modelBuilder.Entity().Property(x => x.LayerId).IsRequired(); modelBuilder.Entity() .HasOne(x => x.CreatedBy) .WithMany() .HasForeignKey(x => x.CreatedById) .OnDelete(DeleteBehavior.Restrict); modelBuilder.Entity() .HasOne(x => x.ModifiedBy) .WithMany() .HasForeignKey(x => x.ModifiedById) .OnDelete(DeleteBehavior.Restrict); modelBuilder.Entity() .HasOne() .WithMany(l => l.Records!) .HasForeignKey(x => x.LayerId) .OnDelete(DeleteBehavior.Cascade); modelBuilder.Entity().HasKey(x => x.Id); modelBuilder.Entity().Property(x => x.RecordId).IsRequired(); modelBuilder.Entity().Property(x => x.LayerId).IsRequired(); modelBuilder.Entity().Property(x => x.ChangedAt).IsRequired(); modelBuilder.Entity().Property(x => x.ChangedById).IsRequired(); modelBuilder.Entity().Property(x => x.ChangeType).IsRequired().HasConversion(); modelBuilder.Entity().Property(x => x.Code).IsRequired().HasMaxLength(50); modelBuilder.Entity().Property(x => x.Desc1).HasMaxLength(10000); modelBuilder.Entity().Property(x => x.ChangedFields).HasMaxLength(200); modelBuilder.Entity().Property(x => x.ChangesSummary).HasMaxLength(4000); // Indexes for efficient history queries modelBuilder.Entity() .HasIndex(x => new { x.RecordId, x.ChangedAt }); modelBuilder.Entity() .HasIndex(x => new { x.LayerId, x.ChangedAt }); modelBuilder.Entity() .HasOne(x => x.ChangedBy) .WithMany() .HasForeignKey(x => x.ChangedById) .OnDelete(DeleteBehavior.Restrict); modelBuilder.Entity().HasKey(x => new { x.LayerId, x.SourceId }); modelBuilder.Entity().Property(x => x.LayerId).IsRequired(); modelBuilder.Entity().Property(x => x.SourceId).IsRequired(); modelBuilder.Entity() .HasOne() .WithMany() .HasForeignKey(x => x.LayerId) .OnDelete(DeleteBehavior.Cascade); modelBuilder.Entity() .HasOne(x => x.Source) .WithMany() .HasForeignKey(x => x.SourceId) .OnDelete(DeleteBehavior.Restrict); modelBuilder.Entity().HasKey(x => x.Id); modelBuilder.Entity().Property(x => x.Name).IsRequired().HasMaxLength(50); modelBuilder.Entity().Property(x => x.Source).IsRequired().HasMaxLength(50); modelBuilder.Entity().Property(x => x.Data).IsRequired(); modelBuilder.Entity().Property(x => x.CreatedAt); modelBuilder.Entity().HasKey(x => x.Id); modelBuilder.Entity().Property(x => x.LayerId).IsRequired(); modelBuilder.Entity().Property(x => x.LayerName).IsRequired().HasMaxLength(200); modelBuilder.Entity().Property(x => x.PluginName).IsRequired().HasMaxLength(100); modelBuilder.Entity().Property(x => x.JobType).IsRequired().HasConversion(); modelBuilder.Entity().Property(x => x.Priority); modelBuilder.Entity().Property(x => x.CreatedAt).IsRequired(); modelBuilder.Entity().Property(x => x.RetryCount); modelBuilder.Entity().Property(x => x.MaxRetries); modelBuilder.Entity().Property(x => x.Status).IsRequired().HasConversion(); modelBuilder.Entity().Property(x => x.LastError).HasMaxLength(1000); modelBuilder.Entity().Property(x => x.LastAttemptAt); modelBuilder.Entity().Property(x => x.CompletedAt); modelBuilder.Entity().Property(x => x.CreatedById).IsRequired(); modelBuilder.Entity().Property(x => x.ModifiedById).IsRequired(); modelBuilder.Entity().Property(x => x.ModifiedAt).IsRequired(); // Configure automatic timestamps for entities with CreatedAt/ModifiedAt ConfigureTimestamps(modelBuilder); } private void ConfigureTimestamps(ModelBuilder modelBuilder) { foreach (var entityType in modelBuilder.Model.GetEntityTypes()) { // Check if entity has CreatedAt property var createdAtProperty = entityType.FindProperty("CreatedAt"); if (createdAtProperty != null) { modelBuilder.Entity(entityType.ClrType) .Property("CreatedAt") .HasDefaultValueSql("GETUTCDATE()"); } // Check if entity has ModifiedAt property var modifiedAtProperty = entityType.FindProperty("ModifiedAt"); if (modifiedAtProperty != null) { modelBuilder.Entity(entityType.ClrType) .Property("ModifiedAt") .HasDefaultValueSql("GETUTCDATE()"); } } } public override int SaveChanges() { UpdateTimestamps(); return base.SaveChanges(); } public override Task SaveChangesAsync(CancellationToken cancellationToken = default) { UpdateTimestamps(); return base.SaveChangesAsync(cancellationToken); } private void UpdateTimestamps() { var entities = ChangeTracker.Entries() .Where(e => e.State == EntityState.Added || e.State == EntityState.Modified); foreach (var entity in entities) { // Try to set CreatedAt for new entities if (entity.State == EntityState.Added) { var createdAtProperty = entity.Properties.FirstOrDefault(p => p.Metadata.Name == "CreatedAt"); if (createdAtProperty != null) { createdAtProperty.CurrentValue = DateTime.UtcNow; } // Ensure IsDeleted and IsCancelled have default values for Layer entities if (entity.Entity is Layer) { var isDeletedProperty = entity.Properties.FirstOrDefault(p => p.Metadata.Name == "IsDeleted"); if (isDeletedProperty != null && isDeletedProperty.CurrentValue == null) { isDeletedProperty.CurrentValue = false; } var isCancelledProperty = entity.Properties.FirstOrDefault(p => p.Metadata.Name == "IsCancelled"); if (isCancelledProperty != null && isCancelledProperty.CurrentValue == null) { isCancelledProperty.CurrentValue = false; } } } // Always update ModifiedAt var modifiedAtProperty = entity.Properties.FirstOrDefault(p => p.Metadata.Name == "ModifiedAt"); if (modifiedAtProperty != null) { modifiedAtProperty.CurrentValue = DateTime.UtcNow; } } } }