Files
Complex/Complex.Infrastructure/ComplexDBContext.cs
alireza 6644db7e78 fix(db): update migration and configure many-to-many relationships to avoid cascade paths
- Regenerated InitialCreate migration to reflect new entity configurations
- Configured many-to-many relationship between CostCycle and Unit with NoAction on Unit side to prevent multiple cascade paths
- Updated UnitCost relationships: Cascade from CostCycle, NoAction from Unit
- Removed commented-out global filter code and replaced with explicit relationship configurations
2026-06-09 02:23:09 +03:30

192 lines
8.1 KiB
C#

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<ComplexDBContext> 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<DateTime>("InsertTime").HasDefaultValue(DateTime.Now);
builder.Entity(entityType.Name).Property<DateTime?>("UpdateTime");
builder.Entity(entityType.Name).Property<DateTime?>("RemoveTime");
builder.Entity(entityType.Name).Property<bool>("IsRemoved").HasDefaultValue(false);
}
}
builder.Entity<ComplexEntity>().HasQueryFilter(b => EF.Property<bool>(b, "IsRemoved") == false);
builder.Entity<Unit>().HasQueryFilter(b => EF.Property<bool>(b, "IsRemoved") == false);
builder.Entity<UnitCost>().HasQueryFilter(b => EF.Property<bool>(b, "IsRemoved") == false);
builder.Entity<CostCycle>().HasQueryFilter(b => EF.Property<bool>(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<CostCycle>()
.HasMany(cc => cc.Units)
.WithMany(u => u.CostCycles)
.UsingEntity<Dictionary<string, object>>(
"CostCycleUnit",
j => j.HasOne<Unit>().WithMany().HasForeignKey("UnitsId").OnDelete(DeleteBehavior.NoAction),
j => j.HasOne<CostCycle>().WithMany().HasForeignKey("CostCyclesId").OnDelete(DeleteBehavior.Cascade));
// Configure UnitCost relationships to avoid multiple cascade paths
// UnitCost -> CostCycle (Cascade) and UnitCost -> Unit (NoAction)
builder.Entity<UnitCost>()
.HasOne(uc => uc.CostCycle)
.WithMany()
.HasForeignKey(uc => uc.CostCycleId)
.OnDelete(DeleteBehavior.Cascade);
builder.Entity<UnitCost>()
.HasOne(uc => uc.Unit)
.WithMany()
.HasForeignKey(uc => uc.UnitId)
.OnDelete(DeleteBehavior.NoAction);
}
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<UnitState> UnitStates { get; set; }
public virtual DbSet<Person> Persons { get; set; }
public virtual DbSet<ComplexPerson> ComplexPersons { get; set; }
public virtual DbSet<BasicTable> BasicTables { get; set; }
public virtual DbSet<Chat> Chats { get; set; }
public virtual DbSet<ComplexEntity> ComplexEntities { get; set; }
public virtual DbSet<ComplexManager> ComplexManagers { get; set; }
public virtual DbSet<ComplexUnitEvent> ComplexUnitEvents { get; set; }
public virtual DbSet<Poster> Posters { get; set; }
public virtual DbSet<Transaction> Transactions { get; set; }
public virtual DbSet<IncomeCostTitle> IncomeCostTitles { get; set; }
public virtual DbSet<TransactionType> TransactionTypes { get; set; }
public virtual DbSet<Unit> Units { get; set; }
public virtual DbSet<CostCycle> CostCycles { get; set; }
public virtual DbSet<UnitCost> UnitCosts { get; set; }
public virtual DbSet<UserToken> UserTokens { get; set; }
public virtual DbSet<SmsCode> SmsCodes { get; set; }
public virtual DbSet<Province> Provinces { get; set; }
public virtual DbSet<City> Cities { get; set; }
protected override void ConfigureConventions(ModelConfigurationBuilder builder)
{
builder.Properties<DateOnly>()
.HaveConversion<DateOnlyConverter>()
.HaveColumnType("date");
base.ConfigureConventions(builder);
}
}
public class DateOnlyConverter : ValueConverter<DateOnly, DateTime>
{
public DateOnlyConverter()
: base(dateOnly =>
dateOnly.ToDateTime(TimeOnly.MinValue),
dateTime => DateOnly.FromDateTime(dateTime))
{ }
}
}