using Complex.Application; using Complex.Domain.Commons; using Complex.Domain.Entities; using Complex.Infrastructure.Extensions; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.ChangeTracking; using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Metadata.Internal; using Microsoft.EntityFrameworkCore.Storage.ValueConversion; using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.Linq; using System.Linq.Expressions; using System.Reflection.Emit; using System.Reflection.Metadata; using System.Text; using System.Threading.Tasks; namespace Complex.Infrastructure { public partial class ComplexDBContext : DbContext, IComplexDBContext { public ComplexDBContext() { } public object? Find(Type entityType, object keyId) { return base.Find(entityType, keyId); } public override EntityEntry Add(object entity) { return base.Add(entity); } public ComplexDBContext(DbContextOptions options) : base(options) { } protected override void OnModelCreating(ModelBuilder builder) { foreach (var entityType in builder.Model.GetEntityTypes()) { if (entityType.ClrType.GetCustomAttributes(typeof(AuditableAttribute), true).Length > 0) { builder.Entity(entityType.Name).Property("InsertTime").HasDefaultValue(DateTime.Now); builder.Entity(entityType.Name).Property("UpdateTime"); builder.Entity(entityType.Name).Property("RemoveTime"); builder.Entity(entityType.Name).Property("IsRemoved").HasDefaultValue(false); } } builder.Entity().HasQueryFilter(b => EF.Property(b, "IsRemoved") == false); builder.Entity().HasQueryFilter(b => EF.Property(b, "IsRemoved") == false); builder.Entity().HasQueryFilter(b => EF.Property(b, "IsRemoved") == false); builder.Entity().HasQueryFilter(b => EF.Property(b, "IsRemoved") == false); // Configure many-to-many between CostCycle and Unit // SQL Server does not allow multiple cascade paths, so one side must use NoAction builder.Entity() .HasMany(cc => cc.Units) .WithMany(u => u.CostCycles) .UsingEntity>( "CostCycleUnit", j => j.HasOne().WithMany().HasForeignKey("UnitsId").OnDelete(DeleteBehavior.NoAction), j => j.HasOne().WithMany().HasForeignKey("CostCyclesId").OnDelete(DeleteBehavior.Cascade)); // Configure UnitCost relationships to avoid multiple cascade paths // UnitCost -> CostCycle (Cascade) and UnitCost -> Unit (NoAction) builder.Entity() .HasOne(uc => uc.CostCycle) .WithMany() .HasForeignKey(uc => uc.CostCycleId) .OnDelete(DeleteBehavior.Cascade); builder.Entity() .HasOne(uc => uc.Unit) .WithMany() .HasForeignKey(uc => uc.UnitId) .OnDelete(DeleteBehavior.NoAction); // ============================================================ // Reference/Lookup tables: These have fixed, meaningful IDs // that are explicitly set in seed data and cross-referenced // by other entities. Identity auto-generation must be disabled. // ============================================================ // Province: ProvinceId (1-31) are stable identifiers builder.Entity() .Property(p => p.ProvinceId) .ValueGeneratedNever(); // City: CityId (101, 102, ...) are meaningful (provinceId * 100 + index) builder.Entity() .Property(c => c.CityId) .ValueGeneratedNever(); // BasicTable (base for TransactionType): Id (1,2) are fixed reference data builder.Entity() .Property(b => b.Id) .ValueGeneratedNever(); // IncomeCostTitle: Id (1-12) are fixed reference data builder.Entity() .Property(i => i.Id) .ValueGeneratedNever(); // UnitState: Id (1-5) are fixed reference data builder.Entity() .Property(u => u.Id) .ValueGeneratedNever(); // Transaction: Seeder uses explicit IDs (1,2,3) that are // cross-referenced by UnitCost.TransactionId builder.Entity() .Property(t => t.Id) .ValueGeneratedNever(); } public override int SaveChanges() { var modifiedEntries = ChangeTracker.Entries() .Where(p => p.State == EntityState.Modified || p.State == EntityState.Added || p.State == EntityState.Deleted ); foreach (var item in modifiedEntries) { var entityType = item.Context.Model.FindEntityType(item.Entity.GetType()); if (entityType != null) { if (entityType.ClrType.GetCustomAttributes(typeof(AuditableAttribute), true).Length == 0) continue; var inserted = entityType.FindProperty("InsertTime"); var updated = entityType.FindProperty("UpdateTime"); var RemoveTime = entityType.FindProperty("RemoveTime"); var IsRemoved = entityType.FindProperty("IsRemoved"); if (item.State == EntityState.Added && inserted != null) { item.Property("InsertTime").CurrentValue = DateTime.Now; } if (item.State == EntityState.Modified && updated != null) { item.Property("UpdateTime").CurrentValue = DateTime.Now; } if (item.State == EntityState.Deleted && RemoveTime != null && IsRemoved != null) { item.Property("RemoveTime").CurrentValue = DateTime.Now; item.Property("IsRemoved").CurrentValue = true; item.State = EntityState.Modified; } } } return base.SaveChanges(); } //{ // var entries = ChangeTracker // .Entries() // .Where(e => // e.State == EntityState.Added // || e.State == EntityState.Modified); // foreach (var entityEntry in entries) // { // entityEntry.Property("UpdateTime").CurrentValue = DateTime.Now; // if (entityEntry.State == EntityState.Added) // { // entityEntry.Property("InsertTime").CurrentValue = DateTime.Now; // } // } // entries = ChangeTracker // .Entries() // .Where(e => // e.State == EntityState.Deleted); // foreach (var entityEntry in entries) // { // entityEntry.State = EntityState.Modified; // entityEntry.Property("RemoveTime").CurrentValue = DateTime.Now; // entityEntry.Property("IsRemoved").CurrentValue = true; // } // return base.SaveChanges(); //} public virtual DbSet UnitStates { get; set; } public virtual DbSet Persons { get; set; } public virtual DbSet ComplexPersons { get; set; } public virtual DbSet BasicTables { get; set; } public virtual DbSet Chats { get; set; } public virtual DbSet ComplexEntities { get; set; } public virtual DbSet ComplexManagers { get; set; } public virtual DbSet ComplexUnitEvents { get; set; } public virtual DbSet Posters { get; set; } public virtual DbSet Transactions { get; set; } public virtual DbSet IncomeCostTitles { get; set; } public virtual DbSet TransactionTypes { get; set; } public virtual DbSet Units { get; set; } public virtual DbSet CostCycles { get; set; } public virtual DbSet UnitCosts { get; set; } public virtual DbSet UserTokens { get; set; } public virtual DbSet SmsCodes { get; set; } public virtual DbSet Provinces { get; set; } public virtual DbSet Cities { get; set; } protected override void ConfigureConventions(ModelConfigurationBuilder builder) { builder.Properties() .HaveConversion() .HaveColumnType("date"); base.ConfigureConventions(builder); } } public class DateOnlyConverter : ValueConverter { public DateOnlyConverter() : base(dateOnly => dateOnly.ToDateTime(TimeOnly.MinValue), dateTime => DateOnly.FromDateTime(dateTime)) { } } }