using MiaoJiZhang.Domain.Entities; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Storage.ValueConversion; namespace MiaoJiZhang.Infrastructure.Persistence; public class AppDbContext(DbContextOptions options) : DbContext(options) { public DbSet Users => Set(); public DbSet UserFeaturePermissions => Set(); public DbSet AiCompanionSettings => Set(); public DbSet Ledgers => Set(); public DbSet Categories => Set(); public DbSet Transactions => Set(); public DbSet Budgets => Set(); public DbSet ChatMessages => Set(); public DbSet AppConfigs => Set(); public DbSet AiPersonas => Set(); public DbSet AiAvatars => Set(); public DbSet Stickers => Set(); protected override void OnModelCreating(ModelBuilder b) { b.Entity(e => { e.HasIndex(x => x.Username).IsUnique(); e.Property(x => x.Username).HasMaxLength(32); e.Property(x => x.Nickname).HasMaxLength(32); // 预留字段索引(唯一但允许 null) e.HasIndex(x => x.Email).IsUnique(); e.HasIndex(x => x.Phone).IsUnique(); e.HasIndex(x => x.WeChatOpenId).IsUnique(); e.HasIndex(x => x.AccountClosureScheduledAt); e.HasOne(x => x.AiCompanion).WithOne(x => x.User) .HasForeignKey(x => x.UserId); }); b.Entity(e => { e.Property(x => x.PermissionKey).HasMaxLength(32); e.HasIndex(x => new { x.UserId, x.PermissionKey }).IsUnique(); e.HasOne(x => x.User).WithMany(x => x.FeaturePermissions) .HasForeignKey(x => x.UserId).OnDelete(DeleteBehavior.Cascade); }); b.Entity(e => { e.Property(x => x.Amount).HasPrecision(12, 2); e.HasIndex(x => new { x.LedgerId, x.OccurredAt }); e.HasIndex(x => new { x.UserId, x.IsDeleted }); e.Property(x => x.ClientRequestId).HasMaxLength(64); e.HasIndex(x => new { x.UserId, x.ClientRequestId }).IsUnique(); e.HasQueryFilter(x => !x.IsDeleted); // 软删除全局过滤 }); b.Entity(e => e.Property(x => x.ColorKey).HasMaxLength(32)); b.Entity(e => { e.Property(x => x.Amount).HasPrecision(12, 2); e.HasIndex(x => new { x.LedgerId, x.Period, x.CategoryId }).IsUnique(); }); b.Entity(e => e.HasIndex(x => new { x.UserId, x.CreatedAt })); b.Entity(e => e.HasIndex(x => x.Key).IsUnique()); b.Entity(e => e.HasIndex(x => x.Key).IsUnique()); b.Entity(e => e.HasIndex(x => x.Key).IsUnique()); b.Entity(e => e.HasIndex(x => x.Key).IsUnique()); // MySQL DATETIME has no timezone metadata. Preserve stored UTC wall-clock // values and restore DateTimeKind.Utc whenever EF materializes them. var utcDateTimeConverter = new ValueConverter( value => value.Kind == DateTimeKind.Local ? value.ToUniversalTime() : DateTime.SpecifyKind(value, DateTimeKind.Utc), value => DateTime.SpecifyKind(value, DateTimeKind.Utc)); var nullableUtcDateTimeConverter = new ValueConverter( value => value.HasValue ? value.Value.Kind == DateTimeKind.Local ? value.Value.ToUniversalTime() : DateTime.SpecifyKind(value.Value, DateTimeKind.Utc) : value, value => value.HasValue ? DateTime.SpecifyKind(value.Value, DateTimeKind.Utc) : value); foreach (var property in b.Model.GetEntityTypes().SelectMany(entity => entity.GetProperties())) { if (property.ClrType == typeof(DateTime)) { property.SetValueConverter(utcDateTimeConverter); } else if (property.ClrType == typeof(DateTime?)) { property.SetValueConverter(nullableUtcDateTimeConverter); } } } }