using Jiaowu.Api.Domain.Academic; using Jiaowu.Api.Domain.Common; using Jiaowu.Api.Domain.Identity; using Jiaowu.Api.Domain.System; using Microsoft.AspNetCore.Identity.EntityFrameworkCore; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Storage.ValueConversion; namespace Jiaowu.Api.Infrastructure.Persistence; public sealed class AppDbContext(DbContextOptions options) : IdentityDbContext(options) { public DbSet Campuses => Set(); public DbSet Colleges => Set(); public DbSet Majors => Set(); public DbSet AdministrativeClasses => Set(); public DbSet Buildings => Set(); public DbSet Classrooms => Set(); public DbSet AcademicTerms => Set(); public DbSet Teachers => Set(); public DbSet Students => Set(); public DbSet CourseCategories => Set(); public DbSet Courses => Set(); public DbSet CoursePrerequisites => Set(); public DbSet CurriculumPlans => Set(); public DbSet CurriculumModules => Set(); public DbSet CurriculumCourses => Set(); public DbSet TeachingTasks => Set(); public DbSet TeachingTaskTeachers => Set(); public DbSet TeachingTaskClasses => Set(); public DbSet TeacherCourseApplications => Set(); public DbSet SchedulePlans => Set(); public DbSet ScheduleEntries => Set(); public DbSet ScheduleTimeSlots => Set(); public DbSet TeachingTaskScheduleConstraints => Set(); public DbSet TeachingTaskAllowedClassrooms => Set(); public DbSet AutomaticScheduleJobs => Set(); public DbSet SchedulePublishJobs => Set(); public DbSet ClassroomReservations => Set(); public DbSet CourseSelectionRounds => Set(); public DbSet CourseSelectionRoundGrades => Set(); public DbSet CourseSelectionOfferings => Set(); public DbSet CourseEnrollments => Set(); public DbSet GradeSheets => Set(); public DbSet GradeRecords => Set(); public DbSet GradeItems => Set(); public DbSet GradeItemScores => Set(); public DbSet AttendanceSheets => Set(); public DbSet AttendanceRecords => Set(); public DbSet AttendanceCheckInAttempts => Set(); public DbSet ExamPlans => Set(); public DbSet ExamArrangementJobs => Set(); public DbSet ExamSignInExportJobs => Set(); public DbSet ExamPublishJobs => Set(); public DbSet ExamSessions => Set(); public DbSet ExamSessionInvigilators => Set(); public DbSet ExamRooms => Set(); public DbSet ExamRoomSessions => Set(); public DbSet ExamSeats => Set(); public DbSet ExamRoomInvigilators => Set(); public DbSet MakeupExamPlans => Set(); public DbSet MakeupExamSessions => Set(); public DbSet MakeupExamSessionInvigilators => Set(); public DbSet MakeupExamEnrollments => Set(); public DbSet MakeupExamAutoJobs => Set(); public DbSet CourseAdjustments => Set(); public DbSet CourseExemptions => Set(); public DbSet DeferredExams => Set(); public DbSet GradeModifications => Set(); public DbSet CourseSubstitutions => Set(); public DbSet WarningRules => Set(); public DbSet WarningRecords => Set(); public DbSet EvaluationSetups => Set(); public DbSet EvaluationDimensions => Set(); public DbSet EvaluationRecords => Set(); public DbSet EvaluationScores => Set(); public DbSet Notifications => Set(); public DbSet MessageDispatches => Set(); public DbSet StudentStatusChanges => Set(); public DbSet GraduationAuditBatches => Set(); public DbSet GraduationAuditResults => Set(); public DbSet DegreeAwardBatches => Set(); public DbSet DegreeAwardResults => Set(); public DbSet GraduationClearanceBatches => Set(); public DbSet GraduationClearanceItems => Set(); public DbSet GraduationClearanceRecords => Set(); public DbSet OfficialDocuments => Set(); public DbSet OfficialDocumentDownloads => Set(); public DbSet AuditLogs => Set(); public DbSet BackgroundJobOutboxMessages => Set(); protected override void ConfigureConventions( ModelConfigurationBuilder configurationBuilder) { base.ConfigureConventions(configurationBuilder); // Connector/NET returns MySQL DATE values as DateTime. An explicit // provider conversion prevents EF from asking the reader for DateOnly. configurationBuilder.Properties() .HaveConversion() .HaveColumnType("date"); // Connector/NET returns MySQL TIME values as TimeSpan. Convert at the // provider boundary so schedule time slots can still use TimeOnly. configurationBuilder.Properties() .HaveConversion() .HaveColumnType("time"); } protected override void OnModelCreating(ModelBuilder builder) { base.OnModelCreating(builder); builder.Entity(entity => { entity.Property(x => x.DisplayName).HasMaxLength(50); entity.Property(x => x.StaffNumber).HasMaxLength(30); entity.Property(x => x.CalendarSubscriptionStamp).HasMaxLength(64); entity.HasIndex(x => x.StaffNumber); }); builder.Entity(entity => { entity.Property(x => x.Description).HasMaxLength(100); }); ConfigureCatalog(builder); ConfigureCatalog(builder); ConfigureCatalog(builder); ConfigureCatalog(builder); ConfigureCatalog(builder); ConfigureCatalog(builder); ConfigureCatalog(builder); ConfigureCatalog(builder); ConfigureCatalog(builder); builder.Entity() .HasOne(x => x.Campus) .WithMany() .HasForeignKey(x => x.CampusId) .OnDelete(DeleteBehavior.Restrict); builder.Entity() .HasOne(x => x.College) .WithMany() .HasForeignKey(x => x.CollegeId) .OnDelete(DeleteBehavior.Restrict); builder.Entity() .HasOne(x => x.Major) .WithMany() .HasForeignKey(x => x.MajorId) .OnDelete(DeleteBehavior.Restrict); builder.Entity(entity => { entity.HasIndex(x => x.CounselorUserId); entity.HasOne(x => x.CounselorUser) .WithMany() .HasForeignKey(x => x.CounselorUserId) .OnDelete(DeleteBehavior.SetNull); }); builder.Entity() .HasOne(x => x.Campus) .WithMany() .HasForeignKey(x => x.CampusId) .OnDelete(DeleteBehavior.Restrict); builder.Entity() .HasOne(x => x.Building) .WithMany() .HasForeignKey(x => x.BuildingId) .OnDelete(DeleteBehavior.Restrict); builder.Entity(entity => { entity.HasIndex(x => x.IsCurrent); entity.HasIndex(x => x.IsArchived); }); builder.Entity(entity => { entity.Property(x => x.TeacherNumber).HasMaxLength(30); entity.Property(x => x.Name).HasMaxLength(50); entity.Property(x => x.Title).HasMaxLength(30); entity.Property(x => x.Phone).HasMaxLength(30); entity.Property(x => x.Email).HasMaxLength(100); entity.Property(x => x.Notes).HasMaxLength(500); entity.HasIndex(x => x.TeacherNumber).IsUnique(); entity.HasIndex(x => new { x.CollegeId, x.Status }); entity.HasOne(x => x.College) .WithMany() .HasForeignKey(x => x.CollegeId) .OnDelete(DeleteBehavior.Restrict); entity.HasOne() .WithMany() .HasForeignKey(x => x.UserId) .OnDelete(DeleteBehavior.SetNull); }); builder.Entity(entity => { entity.Property(x => x.StudentNumber).HasMaxLength(30); entity.Property(x => x.Name).HasMaxLength(50); entity.Property(x => x.Phone).HasMaxLength(30); entity.Property(x => x.Email).HasMaxLength(100); entity.Property(x => x.Notes).HasMaxLength(500); entity.HasIndex(x => x.StudentNumber).IsUnique(); entity.HasIndex(x => new { x.AdministrativeClassId, x.Status }); entity.HasIndex(x => x.EnrollmentYear); entity.HasOne(x => x.AdministrativeClass) .WithMany(x => x.Students) .HasForeignKey(x => x.AdministrativeClassId) .OnDelete(DeleteBehavior.Restrict); entity.HasOne() .WithMany() .HasForeignKey(x => x.UserId) .OnDelete(DeleteBehavior.SetNull); }); builder.Entity(entity => { entity.Property(x => x.EnglishName).HasMaxLength(150); entity.Property(x => x.Credits).HasPrecision(5, 2); entity.Property(x => x.Description).HasMaxLength(1000); entity.HasIndex(x => new { x.CollegeId, x.Nature }); entity.HasIndex(x => x.CourseCategoryId); entity.HasOne(x => x.College) .WithMany() .HasForeignKey(x => x.CollegeId) .OnDelete(DeleteBehavior.Restrict); entity.HasOne(x => x.CourseCategory) .WithMany() .HasForeignKey(x => x.CourseCategoryId) .OnDelete(DeleteBehavior.Restrict); }); builder.Entity(entity => { entity.HasIndex(x => new { x.CourseId, x.PrerequisiteCourseId }) .IsUnique(); entity.HasOne(x => x.Course) .WithMany(x => x.Prerequisites) .HasForeignKey(x => x.CourseId) .OnDelete(DeleteBehavior.Cascade); entity.HasOne(x => x.PrerequisiteCourse) .WithMany(x => x.RequiredByCourses) .HasForeignKey(x => x.PrerequisiteCourseId) .OnDelete(DeleteBehavior.Restrict); }); builder.Entity(entity => { entity.Property(x => x.Name).HasMaxLength(120); entity.Property(x => x.Version).HasMaxLength(30); entity.Property(x => x.TotalCredits).HasPrecision(6, 2); entity.Property(x => x.Description).HasMaxLength(1000); entity.HasIndex(x => new { x.MajorId, x.EffectiveGrade, x.Version }).IsUnique(); entity.HasIndex(x => new { x.Status, x.EffectiveGrade }); entity.HasOne(x => x.Major) .WithMany() .HasForeignKey(x => x.MajorId) .OnDelete(DeleteBehavior.Restrict); }); builder.Entity(entity => { entity.Property(x => x.Code).HasMaxLength(30); entity.Property(x => x.Name).HasMaxLength(100); entity.Property(x => x.RequiredCredits).HasPrecision(6, 2); entity.HasIndex(x => new { x.CurriculumPlanId, x.Code }).IsUnique(); entity.HasOne(x => x.CurriculumPlan) .WithMany(x => x.Modules) .HasForeignKey(x => x.CurriculumPlanId) .OnDelete(DeleteBehavior.Cascade); }); builder.Entity(entity => { entity.Property(x => x.Notes).HasMaxLength(500); entity.HasIndex(x => new { x.CurriculumModuleId, x.CourseId }).IsUnique(); entity.HasOne(x => x.CurriculumModule) .WithMany(x => x.Courses) .HasForeignKey(x => x.CurriculumModuleId) .OnDelete(DeleteBehavior.Cascade); entity.HasOne(x => x.Course) .WithMany() .HasForeignKey(x => x.CourseId) .OnDelete(DeleteBehavior.Restrict); }); builder.Entity(entity => { entity.Property(x => x.TaskNumber).HasMaxLength(40); entity.Property(x => x.Name).HasMaxLength(120); entity.Property(x => x.GenerationBatchCode).HasMaxLength(40); entity.Property(x => x.Notes).HasMaxLength(500); entity.HasIndex(x => x.TaskNumber).IsUnique(); entity.HasIndex(x => new { x.AcademicTermId, x.Status }); entity.HasOne(x => x.AcademicTerm) .WithMany() .HasForeignKey(x => x.AcademicTermId) .OnDelete(DeleteBehavior.Restrict); entity.HasOne(x => x.Course) .WithMany() .HasForeignKey(x => x.CourseId) .OnDelete(DeleteBehavior.Restrict); }); builder.Entity(entity => { entity.HasKey(x => new { x.TeachingTaskId, x.TeacherId }); entity.HasOne(x => x.TeachingTask) .WithMany(x => x.Teachers) .HasForeignKey(x => x.TeachingTaskId) .OnDelete(DeleteBehavior.Cascade); entity.HasOne(x => x.Teacher) .WithMany() .HasForeignKey(x => x.TeacherId) .OnDelete(DeleteBehavior.Restrict); }); builder.Entity(entity => { entity.HasKey(x => new { x.TeachingTaskId, x.AdministrativeClassId }); entity.HasOne(x => x.TeachingTask) .WithMany(x => x.Classes) .HasForeignKey(x => x.TeachingTaskId) .OnDelete(DeleteBehavior.Cascade); entity.HasOne(x => x.AdministrativeClass) .WithMany() .HasForeignKey(x => x.AdministrativeClassId) .OnDelete(DeleteBehavior.Restrict); }); builder.Entity(entity => { entity.Property(x => x.Statement).HasMaxLength(500); entity.Property(x => x.ReviewComment).HasMaxLength(500); entity.HasIndex(x => new { x.AcademicTermId, x.TeacherId, x.CourseId }).IsUnique(); entity.HasIndex(x => new { x.Status, x.AcademicTermId }); entity.HasOne(x => x.AcademicTerm) .WithMany() .HasForeignKey(x => x.AcademicTermId) .OnDelete(DeleteBehavior.Restrict); entity.HasOne(x => x.Teacher) .WithMany() .HasForeignKey(x => x.TeacherId) .OnDelete(DeleteBehavior.Cascade); entity.HasOne(x => x.Course) .WithMany() .HasForeignKey(x => x.CourseId) .OnDelete(DeleteBehavior.Restrict); entity.HasOne() .WithMany() .HasForeignKey(x => x.ReviewedByUserId) .OnDelete(DeleteBehavior.SetNull); }); builder.Entity(entity => { entity.Property(x => x.Name).HasMaxLength(120); entity.Property(x => x.Version).HasMaxLength(30); entity.Property(x => x.Notes).HasMaxLength(500); entity.HasIndex(x => new { x.AcademicTermId, x.Version }).IsUnique(); entity.HasIndex(x => new { x.AcademicTermId, x.Status }); entity.HasOne(x => x.AcademicTerm) .WithMany() .HasForeignKey(x => x.AcademicTermId) .OnDelete(DeleteBehavior.Restrict); }); builder.Entity(entity => { entity.Property(x => x.Notes).HasMaxLength(500); entity.HasIndex(x => new { x.SchedulePlanId, x.DayOfWeek, x.StartPeriod }); entity.HasOne(x => x.SchedulePlan) .WithMany(x => x.Entries) .HasForeignKey(x => x.SchedulePlanId) .OnDelete(DeleteBehavior.Cascade); entity.HasOne(x => x.TeachingTask) .WithMany() .HasForeignKey(x => x.TeachingTaskId) .OnDelete(DeleteBehavior.Restrict); entity.HasOne(x => x.Classroom) .WithMany() .HasForeignKey(x => x.ClassroomId) .OnDelete(DeleteBehavior.SetNull); }); builder.Entity(entity => { entity.Property(x => x.Name).HasMaxLength(40); entity.HasIndex(x => new { x.AcademicTermId, x.PeriodNumber }).IsUnique(); entity.HasOne(x => x.AcademicTerm) .WithMany() .HasForeignKey(x => x.AcademicTermId) .OnDelete(DeleteBehavior.Cascade); }); builder.Entity(entity => { entity.Property(x => x.AllowedDayOfWeeks).HasMaxLength(20); entity.HasIndex(x => x.TeachingTaskId).IsUnique(); entity.HasOne(x => x.TeachingTask) .WithMany() .HasForeignKey(x => x.TeachingTaskId) .OnDelete(DeleteBehavior.Cascade); entity.HasOne(x => x.RequiredCampus) .WithMany() .HasForeignKey(x => x.RequiredCampusId) .OnDelete(DeleteBehavior.Restrict); entity.HasOne(x => x.RequiredBuilding) .WithMany() .HasForeignKey(x => x.RequiredBuildingId) .OnDelete(DeleteBehavior.Restrict); }); builder.Entity(entity => { entity.HasKey(x => new { x.TeachingTaskScheduleConstraintId, x.ClassroomId }); entity.HasOne(x => x.TeachingTaskScheduleConstraint) .WithMany(x => x.AllowedClassrooms) .HasForeignKey(x => x.TeachingTaskScheduleConstraintId) .OnDelete(DeleteBehavior.Cascade); entity.HasOne(x => x.Classroom) .WithMany() .HasForeignKey(x => x.ClassroomId) .OnDelete(DeleteBehavior.Restrict); }); builder.Entity(entity => { entity.Property(x => x.ErrorMessage).HasMaxLength(2000); entity.HasIndex(x => x.ActiveSchedulePlanId).IsUnique(); entity.HasIndex(x => new { x.SchedulePlanId, x.CreatedAt }); entity.HasIndex(x => new { x.Status, x.CreatedAt }); entity.HasIndex(x => x.RequestedByUserId); entity.HasOne(x => x.SchedulePlan) .WithMany() .HasForeignKey(x => x.SchedulePlanId) .OnDelete(DeleteBehavior.Cascade); entity.HasOne() .WithMany() .HasForeignKey(x => x.RequestedByUserId) .OnDelete(DeleteBehavior.SetNull); }); builder.Entity(entity => { entity.Property(x => x.CurrentStep).HasMaxLength(200); entity.Property(x => x.ErrorMessage).HasMaxLength(2000); entity.HasIndex(x => x.ActiveAcademicTermId).IsUnique(); entity.HasIndex(x => new { x.SchedulePlanId, x.CreatedAt }); entity.HasIndex(x => new { x.Status, x.CreatedAt }); entity.HasIndex(x => x.RequestedByUserId); entity.HasOne(x => x.SchedulePlan) .WithMany() .HasForeignKey(x => x.SchedulePlanId) .OnDelete(DeleteBehavior.Cascade); entity.HasOne() .WithMany() .HasForeignKey(x => x.RequestedByUserId) .OnDelete(DeleteBehavior.SetNull); }); builder.Entity(entity => { entity.Property(x => x.ApplicantName).HasMaxLength(50); entity.Property(x => x.Purpose).HasMaxLength(200); entity.Property(x => x.ContactPhone).HasMaxLength(30); entity.Property(x => x.Notes).HasMaxLength(500); entity.Property(x => x.ReviewComment).HasMaxLength(500); entity.HasIndex(x => new { x.ApplicantUserId, x.Status, x.CreatedAt }); entity.HasIndex(x => new { x.ApplicantCollegeId, x.Status, x.ReservationDate }); entity.HasIndex(x => new { x.ClassroomId, x.ReservationDate, x.Status, x.StartPeriod }); entity.HasOne(x => x.ApplicantUser) .WithMany() .HasForeignKey(x => x.ApplicantUserId) .OnDelete(DeleteBehavior.Restrict); entity.HasOne(x => x.ApplicantCollege) .WithMany() .HasForeignKey(x => x.ApplicantCollegeId) .OnDelete(DeleteBehavior.Restrict); entity.HasOne(x => x.AcademicTerm) .WithMany() .HasForeignKey(x => x.AcademicTermId) .OnDelete(DeleteBehavior.Restrict); entity.HasOne(x => x.Classroom) .WithMany() .HasForeignKey(x => x.ClassroomId) .OnDelete(DeleteBehavior.Restrict); entity.HasOne(x => x.ReviewedByUser) .WithMany() .HasForeignKey(x => x.ReviewedByUserId) .OnDelete(DeleteBehavior.SetNull); }); builder.Entity(entity => { entity.Property(x => x.Name).HasMaxLength(120); entity.Property(x => x.MaxCredits).HasPrecision(6, 1); entity.Property(x => x.Notes).HasMaxLength(500); entity.HasIndex(x => new { x.AcademicTermId, x.Status }); entity.HasOne(x => x.AcademicTerm) .WithMany() .HasForeignKey(x => x.AcademicTermId) .OnDelete(DeleteBehavior.Restrict); }); builder.Entity(entity => { entity.HasIndex(x => new { x.CourseSelectionRoundId, x.Grade }) .IsUnique(); entity.HasIndex(x => x.Grade); entity.HasOne(x => x.CourseSelectionRound) .WithMany(x => x.EligibleGrades) .HasForeignKey(x => x.CourseSelectionRoundId) .OnDelete(DeleteBehavior.Cascade); }); builder.Entity(entity => { entity.Property(x => x.Notes).HasMaxLength(500); entity.HasIndex(x => new { x.CourseSelectionRoundId, x.TeachingTaskId }).IsUnique(); entity.HasOne(x => x.CourseSelectionRound) .WithMany(x => x.Offerings) .HasForeignKey(x => x.CourseSelectionRoundId) .OnDelete(DeleteBehavior.Cascade); entity.HasOne(x => x.TeachingTask) .WithMany() .HasForeignKey(x => x.TeachingTaskId) .OnDelete(DeleteBehavior.Restrict); }); builder.Entity(entity => { entity.HasIndex(x => new { x.CourseSelectionOfferingId, x.StudentId }) .IsUnique(); entity.HasIndex(x => new { x.StudentId, x.Status }); entity.HasIndex(x => new { x.CourseSelectionOfferingId, x.Status, x.WaitlistedAt }).HasDatabaseName("IX_CE_Offering_Status_WaitlistedAt"); entity.HasOne(x => x.CourseSelectionOffering) .WithMany(x => x.Enrollments) .HasForeignKey(x => x.CourseSelectionOfferingId) .OnDelete(DeleteBehavior.Cascade); entity.HasOne(x => x.Student) .WithMany() .HasForeignKey(x => x.StudentId) .OnDelete(DeleteBehavior.Restrict); }); builder.Entity(entity => { entity.Property(x => x.RegularWeight).HasPrecision(5, 1); entity.Property(x => x.FinalWeight).HasPrecision(5, 1); entity.Property(x => x.ReviewComment).HasMaxLength(500); entity.HasIndex(x => x.TeachingTaskId).IsUnique(); entity.HasIndex(x => x.Status); entity.HasOne(x => x.TeachingTask) .WithMany() .HasForeignKey(x => x.TeachingTaskId) .OnDelete(DeleteBehavior.Restrict); }); builder.Entity(entity => { entity.Property(x => x.Name).HasMaxLength(60); entity.Property(x => x.Weight).HasPrecision(5, 1); entity.HasIndex(x => new { x.GradeSheetId, x.SortOrder }); entity.HasOne(x => x.GradeSheet) .WithMany(x => x.Items) .HasForeignKey(x => x.GradeSheetId) .OnDelete(DeleteBehavior.Cascade); }); builder.Entity(entity => { entity.Property(x => x.RegularScore).HasPrecision(5, 1); entity.Property(x => x.FinalScore).HasPrecision(5, 1); entity.Property(x => x.TotalScore).HasPrecision(5, 1); entity.Property(x => x.GradePoint).HasPrecision(3, 1); entity.Property(x => x.Notes).HasMaxLength(300); entity.HasIndex(x => new { x.GradeSheetId, x.StudentId }).IsUnique(); entity.HasIndex(x => new { x.StudentId, x.TotalScore }); entity.HasOne(x => x.GradeSheet) .WithMany(x => x.Records) .HasForeignKey(x => x.GradeSheetId) .OnDelete(DeleteBehavior.Cascade); entity.HasOne(x => x.Student) .WithMany() .HasForeignKey(x => x.StudentId) .OnDelete(DeleteBehavior.Restrict); }); builder.Entity(entity => { entity.HasKey(x => new { x.GradeRecordId, x.GradeItemId }); entity.Property(x => x.Score).HasPrecision(5, 1); entity.HasOne(x => x.GradeRecord) .WithMany(x => x.ItemScores) .HasForeignKey(x => x.GradeRecordId) .OnDelete(DeleteBehavior.Cascade); entity.HasOne(x => x.GradeItem) .WithMany(x => x.Scores) .HasForeignKey(x => x.GradeItemId) .OnDelete(DeleteBehavior.Restrict); }); builder.Entity(entity => { entity.Property(x => x.Name).HasMaxLength(120); entity.Property(x => x.CheckInToken).HasMaxLength(64); entity.Property(x => x.TargetLatitude).HasPrecision(10, 7); entity.Property(x => x.TargetLongitude).HasPrecision(10, 7); entity.Property(x => x.Notes).HasMaxLength(500); entity.HasIndex(x => new { x.TeachingTaskId, x.AttendanceDate }); entity.HasIndex(x => x.CheckInToken).IsUnique(); entity.HasOne(x => x.TeachingTask) .WithMany() .HasForeignKey(x => x.TeachingTaskId) .OnDelete(DeleteBehavior.Restrict); }); builder.Entity(entity => { entity.HasKey(x => new { x.AttendanceSheetId, x.StudentId }); entity.Property(x => x.Notes).HasMaxLength(300); entity.Property(x => x.AppealReason).HasMaxLength(500); entity.Property(x => x.AppealReviewComment).HasMaxLength(300); entity.Property(x => x.CheckInLatitude).HasPrecision(10, 7); entity.Property(x => x.CheckInLongitude).HasPrecision(10, 7); entity.HasIndex(x => x.AppealStatus); entity.HasOne(x => x.AttendanceSheet) .WithMany(x => x.Records) .HasForeignKey(x => x.AttendanceSheetId) .OnDelete(DeleteBehavior.Cascade); entity.HasOne(x => x.Student) .WithMany() .HasForeignKey(x => x.StudentId) .OnDelete(DeleteBehavior.Restrict); }); builder.Entity(entity => { entity.Property(x => x.FailureCode).HasMaxLength(64); entity.Property(x => x.DeviceIdentifierHash).HasMaxLength(64); entity.Property(x => x.DevicePlatform).HasMaxLength(32); entity.Property(x => x.IpAddress).HasMaxLength(64); entity.Property(x => x.UserAgent).HasMaxLength(500); entity.Property(x => x.RiskFlags).HasMaxLength(300); entity.Property(x => x.Latitude).HasPrecision(10, 7); entity.Property(x => x.Longitude).HasPrecision(10, 7); entity.HasIndex(x => new { x.AttendanceSheetId, x.StudentId, x.CreatedAt }); entity.HasIndex(x => new { x.DeviceIdentifierHash, x.CreatedAt }); entity.HasIndex(x => new { x.IpAddress, x.CreatedAt }); entity.HasOne(x => x.AttendanceSheet) .WithMany(x => x.CheckInAttempts) .HasForeignKey(x => x.AttendanceSheetId) .OnDelete(DeleteBehavior.Cascade); entity.HasOne(x => x.Student) .WithMany() .HasForeignKey(x => x.StudentId) .OnDelete(DeleteBehavior.Restrict); }); builder.Entity(entity => { entity.Property(x => x.Name).HasMaxLength(120); entity.Property(x => x.Notes).HasMaxLength(500); entity.HasIndex(x => new { x.AcademicTermId, x.Status }); entity.HasOne(x => x.AcademicTerm).WithMany() .HasForeignKey(x => x.AcademicTermId).OnDelete(DeleteBehavior.Restrict); }); builder.Entity(entity => { entity.Property(x => x.Notes).HasMaxLength(500); entity.HasIndex(x => new { x.ExamPlanId, x.StartsAt }); entity.HasIndex(x => x.TeachingTaskId); entity.HasIndex(x => new { x.ExamPlanId, x.ExamDate }); entity.HasIndex(x => x.RequiredBuildingId); entity.Property(x => x.RequiredBuildingIds).HasColumnType("longtext"); entity.HasOne(x => x.ExamPlan).WithMany(x => x.Sessions) .HasForeignKey(x => x.ExamPlanId).OnDelete(DeleteBehavior.Cascade); entity.HasOne(x => x.TeachingTask).WithMany() .HasForeignKey(x => x.TeachingTaskId).OnDelete(DeleteBehavior.Restrict); entity.HasOne(x => x.Classroom).WithMany() .HasForeignKey(x => x.ClassroomId).OnDelete(DeleteBehavior.SetNull); entity.HasOne(x => x.RequiredBuilding).WithMany() .HasForeignKey(x => x.RequiredBuildingId).OnDelete(DeleteBehavior.SetNull); }); builder.Entity(entity => { entity.HasKey(x => new { x.ExamSessionId, x.TeacherId }); entity.HasOne(x => x.ExamSession).WithMany(x => x.Invigilators) .HasForeignKey(x => x.ExamSessionId).OnDelete(DeleteBehavior.Cascade); entity.HasOne(x => x.Teacher).WithMany() .HasForeignKey(x => x.TeacherId).OnDelete(DeleteBehavior.Restrict); }); builder.Entity(entity => { entity.ToTable("ExamRooms"); entity.HasIndex(x => new { x.ExamPlanId, x.StartsAt }) .HasDatabaseName("IX_ExamRooms_Plan_Time"); entity.HasIndex(x => new { x.ExamPlanId, x.ClassroomId, x.StartsAt }) .HasDatabaseName("IX_ExamRooms_Plan_Room_Time"); entity.HasIndex(x => x.CourseId) .HasDatabaseName("IX_ExamRooms_CourseId"); entity.HasOne(x => x.ExamPlan).WithMany(x => x.Rooms) .HasForeignKey(x => x.ExamPlanId).OnDelete(DeleteBehavior.Cascade); entity.HasOne(x => x.Course).WithMany() .HasForeignKey(x => x.CourseId).OnDelete(DeleteBehavior.Restrict); entity.HasOne(x => x.Classroom).WithMany() .HasForeignKey(x => x.ClassroomId).OnDelete(DeleteBehavior.Restrict); }); builder.Entity(entity => { entity.ToTable("ExamRoomSessions"); entity.HasKey(x => new { x.ExamRoomId, x.ExamSessionId }); entity.HasIndex(x => x.ExamSessionId) .HasDatabaseName("IX_ExamRoomSessions_SessionId"); entity.HasOne(x => x.ExamRoom).WithMany(x => x.SessionLinks) .HasForeignKey(x => x.ExamRoomId).OnDelete(DeleteBehavior.Cascade); entity.HasOne(x => x.ExamSession).WithMany(x => x.RoomLinks) .HasForeignKey(x => x.ExamSessionId).OnDelete(DeleteBehavior.Restrict); }); builder.Entity(entity => { entity.ToTable("ExamSeats"); entity.HasKey(x => new { x.ExamRoomId, x.StudentId }); entity.HasIndex(x => new { x.ExamSessionId, x.StudentId }) .IsUnique() .HasDatabaseName("UX_ExamSeats_Session_Student"); entity.HasIndex(x => x.StudentId) .HasDatabaseName("IX_ExamSeats_StudentId"); entity.HasOne(x => x.ExamRoom).WithMany(x => x.Seats) .HasForeignKey(x => x.ExamRoomId).OnDelete(DeleteBehavior.Cascade); entity.HasOne(x => x.ExamSession).WithMany(x => x.SeatAssignments) .HasForeignKey(x => x.ExamSessionId).OnDelete(DeleteBehavior.Restrict); entity.HasOne(x => x.Student).WithMany() .HasForeignKey(x => x.StudentId).OnDelete(DeleteBehavior.Restrict); }); builder.Entity(entity => { entity.ToTable("ExamRoomInvigilators"); entity.HasKey(x => new { x.ExamRoomId, x.TeacherId }); entity.HasIndex(x => x.TeacherId) .HasDatabaseName("IX_ExamRoomInvigilators_TeacherId"); entity.HasOne(x => x.ExamRoom).WithMany(x => x.Invigilators) .HasForeignKey(x => x.ExamRoomId).OnDelete(DeleteBehavior.Cascade); entity.HasOne(x => x.Teacher).WithMany() .HasForeignKey(x => x.TeacherId).OnDelete(DeleteBehavior.Restrict); }); builder.Entity(entity => { entity.Property(x => x.CurrentStep).HasMaxLength(200); entity.Property(x => x.ResultMessage).HasMaxLength(2000); entity.Property(x => x.ErrorMessage).HasMaxLength(2000); entity.HasIndex(x => new { x.Kind, x.ActivePlanId }) .IsUnique() .HasDatabaseName("UX_ExamArrangementJobs_Kind_ActivePlan"); entity.HasIndex(x => new { x.Kind, x.PlanId, x.CreatedAt }) .HasDatabaseName("IX_ExamArrangementJobs_Kind_Plan_CreatedAt"); entity.HasIndex(x => new { x.Status, x.CreatedAt }); entity.HasIndex(x => x.RequestedByUserId); }); builder.Entity(entity => { entity.Property(x => x.CurrentStep).HasMaxLength(200); entity.Property(x => x.FileName).HasMaxLength(200); entity.Property(x => x.ErrorMessage).HasMaxLength(2000); entity.HasIndex(x => new { x.PlanId, x.CreatedAt }); entity.HasIndex(x => new { x.Status, x.CreatedAt }); entity.HasIndex(x => x.RequestedByUserId); }); builder.Entity(entity => { entity.Property(x => x.Name).HasMaxLength(120); entity.Property(x => x.Notes).HasMaxLength(500); entity.HasIndex(x => new { x.AcademicTermId, x.Status }); entity.HasOne(x => x.AcademicTerm).WithMany() .HasForeignKey(x => x.AcademicTermId).OnDelete(DeleteBehavior.Restrict); }); builder.Entity(entity => { entity.Property(x => x.Notes).HasMaxLength(500); entity.HasIndex(x => new { x.MakeupExamPlanId, x.StartsAt }); entity.HasIndex(x => x.TeachingTaskId); entity.HasIndex(x => new { x.MakeupExamPlanId, x.ExamDate }); entity.HasIndex(x => x.RequiredBuildingId); entity.Property(x => x.RequiredBuildingIds).HasColumnType("longtext"); entity.HasOne(x => x.MakeupExamPlan).WithMany(x => x.Sessions) .HasForeignKey(x => x.MakeupExamPlanId).OnDelete(DeleteBehavior.Cascade); entity.HasOne(x => x.TeachingTask).WithMany() .HasForeignKey(x => x.TeachingTaskId).OnDelete(DeleteBehavior.Restrict); entity.HasOne(x => x.Classroom).WithMany() .HasForeignKey(x => x.ClassroomId).OnDelete(DeleteBehavior.SetNull); entity.HasOne(x => x.RequiredBuilding).WithMany() .HasForeignKey(x => x.RequiredBuildingId).OnDelete(DeleteBehavior.SetNull); }); builder.Entity(entity => { entity.HasKey(x => new { x.MakeupExamSessionId, x.TeacherId }); entity.HasOne(x => x.MakeupExamSession).WithMany(x => x.Invigilators) .HasForeignKey(x => x.MakeupExamSessionId).OnDelete(DeleteBehavior.Cascade); entity.HasOne(x => x.Teacher).WithMany() .HasForeignKey(x => x.TeacherId).OnDelete(DeleteBehavior.Restrict); }); builder.Entity(entity => { entity.HasKey(x => new { x.MakeupExamSessionId, x.StudentId }); entity.HasIndex(x => new { x.StudentId, x.MakeupExamSessionId }); entity.Property(x => x.MakeupScore).HasPrecision(5, 1); entity.HasOne(x => x.MakeupExamSession).WithMany(x => x.Enrollments) .HasForeignKey(x => x.MakeupExamSessionId).OnDelete(DeleteBehavior.Cascade); entity.HasOne(x => x.Student).WithMany() .HasForeignKey(x => x.StudentId).OnDelete(DeleteBehavior.Restrict); entity.HasOne(x => x.SourceGradeRecord).WithMany() .HasForeignKey(x => x.SourceGradeRecordId).OnDelete(DeleteBehavior.SetNull); entity.HasOne(x => x.SourceDeferredExam).WithMany() .HasForeignKey(x => x.SourceDeferredExamId).OnDelete(DeleteBehavior.SetNull); }); builder.Entity(entity => { entity.Property(x => x.ErrorMessage).HasMaxLength(2000); entity.HasIndex(x => new { x.MakeupExamPlanId, x.CreatedAt }); entity.HasIndex(x => new { x.Status, x.CreatedAt }); entity.HasOne(x => x.MakeupExamPlan).WithMany() .HasForeignKey(x => x.MakeupExamPlanId).OnDelete(DeleteBehavior.Cascade); }); builder.Entity(entity => { entity.Property(x => x.Reason).HasMaxLength(1000); entity.Property(x => x.ReviewComment).HasMaxLength(500); entity.HasIndex(x => new { x.StudentId, x.State }); entity.HasOne(x => x.Student).WithMany() .HasForeignKey(x => x.StudentId).OnDelete(DeleteBehavior.Restrict); }); builder.Entity(entity => { entity.Property(x => x.Name).HasMaxLength(120); entity.Property(x => x.Notes).HasMaxLength(500); entity.HasIndex(x => new { x.GraduationYear, x.EnrollmentYear }); entity.HasIndex(x => x.Status); }); builder.Entity(entity => { entity.Property(x => x.RequiredCredits).HasPrecision(6, 2); entity.Property(x => x.EarnedCredits).HasPrecision(6, 2); entity.Property(x => x.MissingCourseNames).HasMaxLength(2000); entity.Property(x => x.ReviewComment).HasMaxLength(500); entity.HasIndex(x => new { x.GraduationAuditBatchId, x.StudentId }) .IsUnique(); entity.HasIndex(x => new { x.Conclusion, x.IsOverridden }); entity.HasOne(x => x.GraduationAuditBatch).WithMany(x => x.Results) .HasForeignKey(x => x.GraduationAuditBatchId) .OnDelete(DeleteBehavior.Cascade); entity.HasOne(x => x.Student).WithMany() .HasForeignKey(x => x.StudentId).OnDelete(DeleteBehavior.Restrict); entity.HasOne(x => x.CurriculumPlan).WithMany() .HasForeignKey(x => x.CurriculumPlanId).OnDelete(DeleteBehavior.SetNull); }); builder.Entity(entity => { entity.Property(x => x.Name).HasMaxLength(120); entity.Property(x => x.DegreeName).HasMaxLength(80); entity.Property(x => x.MinimumGradePoint).HasPrecision(3, 2); entity.Property(x => x.Notes).HasMaxLength(500); entity.HasIndex(x => new { x.GraduationYear, x.Status }); }); builder.Entity(entity => { entity.Property(x => x.AverageGradePoint).HasPrecision(4, 2); entity.Property(x => x.ExceptionReason).HasMaxLength(500); entity.Property(x => x.ReviewComment).HasMaxLength(500); entity.HasIndex(x => new { x.DegreeAwardBatchId, x.StudentId }).IsUnique(); entity.HasIndex(x => new { x.Conclusion, x.IsOverridden }); entity.HasOne(x => x.DegreeAwardBatch).WithMany(x => x.Results) .HasForeignKey(x => x.DegreeAwardBatchId).OnDelete(DeleteBehavior.Cascade); entity.HasOne(x => x.Student).WithMany() .HasForeignKey(x => x.StudentId).OnDelete(DeleteBehavior.Restrict); entity.HasOne(x => x.GraduationAuditResult).WithMany() .HasForeignKey(x => x.GraduationAuditResultId) .OnDelete(DeleteBehavior.Restrict); }); builder.Entity(entity => { entity.Property(x => x.Name).HasMaxLength(120); entity.Property(x => x.Notes).HasMaxLength(500); entity.HasIndex(x => new { x.GraduationYear, x.Status }); }); builder.Entity(entity => { entity.Property(x => x.Code).HasMaxLength(30); entity.Property(x => x.Name).HasMaxLength(100); entity.Property(x => x.ResponsibleUnit).HasMaxLength(100); entity.Property(x => x.ResponsibleRole).HasMaxLength(30); entity.HasIndex(x => new { x.GraduationClearanceBatchId, x.Code }).IsUnique(); entity.HasOne(x => x.GraduationClearanceBatch).WithMany(x => x.Items) .HasForeignKey(x => x.GraduationClearanceBatchId) .OnDelete(DeleteBehavior.Cascade); }); builder.Entity(entity => { entity.Property(x => x.Notes).HasMaxLength(500); entity.HasIndex(x => new { x.GraduationClearanceItemId, x.StudentId }) .IsUnique(); entity.HasIndex(x => new { x.StudentId, x.Status }); entity.HasOne(x => x.GraduationClearanceItem).WithMany(x => x.Records) .HasForeignKey(x => x.GraduationClearanceItemId) .OnDelete(DeleteBehavior.Cascade); entity.HasOne(x => x.Student).WithMany() .HasForeignKey(x => x.StudentId).OnDelete(DeleteBehavior.Restrict); }); builder.Entity(entity => { entity.Property(x => x.Reason).HasMaxLength(500); entity.Property(x => x.ReviewComment).HasMaxLength(500); entity.HasIndex(x => new { x.Status, x.CreatedAt }); entity.HasIndex(x => new { x.StudentId, x.TeachingTaskId }).IsUnique(); entity.HasOne(x => x.Student).WithMany() .HasForeignKey(x => x.StudentId).OnDelete(DeleteBehavior.Restrict); entity.HasOne(x => x.TeachingTask).WithMany() .HasForeignKey(x => x.TeachingTaskId).OnDelete(DeleteBehavior.Restrict); }); builder.Entity(entity => { entity.Property(x => x.Reason).HasMaxLength(500); entity.Property(x => x.ReviewComment).HasMaxLength(500); entity.HasIndex(x => new { x.Status, x.CreatedAt }); entity.HasIndex(x => new { x.StudentId, x.TeachingTaskId }).IsUnique(); entity.HasOne(x => x.Student).WithMany() .HasForeignKey(x => x.StudentId).OnDelete(DeleteBehavior.Restrict); entity.HasOne(x => x.TeachingTask).WithMany() .HasForeignKey(x => x.TeachingTaskId).OnDelete(DeleteBehavior.Restrict); }); builder.Entity(entity => { entity.Property(x => x.CurrentScore).HasPrecision(5, 1); entity.Property(x => x.RequestedScore).HasPrecision(5, 1); entity.Property(x => x.Reason).HasMaxLength(500); entity.Property(x => x.ReviewComment).HasMaxLength(500); entity.HasIndex(x => new { x.Status, x.CreatedAt }); entity.HasOne(x => x.GradeRecord).WithMany() .HasForeignKey(x => x.GradeRecordId).OnDelete(DeleteBehavior.Restrict); }); builder.Entity(entity => { entity.Property(x => x.Name).HasMaxLength(100); entity.Property(x => x.Threshold).HasPrecision(7, 2); entity.Property(x => x.Description).HasMaxLength(300); entity.HasIndex(x => new { x.AcademicTermId, x.Type }).IsUnique(); entity.HasOne(x => x.AcademicTerm).WithMany() .HasForeignKey(x => x.AcademicTermId).OnDelete(DeleteBehavior.Cascade); }); builder.Entity(entity => { entity.Property(x => x.TriggerValue).HasPrecision(7, 2); entity.Property(x => x.Detail).HasMaxLength(1000); entity.Property(x => x.AcknowledgeComment).HasMaxLength(300); entity.HasIndex(x => new { x.StudentId, x.AcademicTermId, x.Type }).IsUnique(); entity.HasIndex(x => x.Status); entity.HasOne(x => x.Student).WithMany() .HasForeignKey(x => x.StudentId).OnDelete(DeleteBehavior.Restrict); }); // ── Evaluation ── builder.Entity(entity => { entity.Property(x => x.Name).HasMaxLength(120); entity.HasIndex(x => new { x.AcademicTermId, x.Status }); entity.HasOne(x => x.AcademicTerm).WithMany() .HasForeignKey(x => x.AcademicTermId).OnDelete(DeleteBehavior.Restrict); }); builder.Entity(entity => { entity.Property(x => x.Name).HasMaxLength(60); entity.HasIndex(x => new { x.EvaluationSetupId, x.SortOrder }); entity.HasOne(x => x.EvaluationSetup).WithMany(x => x.Dimensions) .HasForeignKey(x => x.EvaluationSetupId).OnDelete(DeleteBehavior.Cascade); }); builder.Entity(entity => { entity.HasIndex(x => new { x.EvaluationSetupId, x.StudentId, x.TeachingTaskId }).IsUnique(); entity.HasOne(x => x.EvaluationSetup).WithMany(x => x.Records) .HasForeignKey(x => x.EvaluationSetupId).OnDelete(DeleteBehavior.Restrict); entity.HasOne(x => x.Student).WithMany() .HasForeignKey(x => x.StudentId).OnDelete(DeleteBehavior.Restrict); entity.HasOne(x => x.TeachingTask).WithMany() .HasForeignKey(x => x.TeachingTaskId).OnDelete(DeleteBehavior.Restrict); }); builder.Entity(entity => { entity.HasKey(x => new { x.EvaluationRecordId, x.EvaluationDimensionId }); entity.HasOne(x => x.EvaluationRecord).WithMany(x => x.Scores) .HasForeignKey(x => x.EvaluationRecordId).OnDelete(DeleteBehavior.Cascade); entity.HasOne(x => x.EvaluationDimension).WithMany(x => x.Scores) .HasForeignKey(x => x.EvaluationDimensionId).OnDelete(DeleteBehavior.Restrict); }); builder.Entity(entity => { entity.Property(x => x.Reason).HasMaxLength(500); entity.Property(x => x.ReviewComment).HasMaxLength(500); entity.HasIndex(x => new { x.Status, x.CreatedAt }); entity.HasIndex(x => new { x.StudentId, x.OriginalCourseId }).IsUnique(); entity.HasOne(x => x.Student).WithMany() .HasForeignKey(x => x.StudentId).OnDelete(DeleteBehavior.Restrict); entity.HasOne(x => x.OriginalCourse).WithMany() .HasForeignKey(x => x.OriginalCourseId).OnDelete(DeleteBehavior.Restrict); entity.HasOne(x => x.SubstituteCourse).WithMany() .HasForeignKey(x => x.SubstituteCourseId).OnDelete(DeleteBehavior.Restrict); }); builder.Entity(entity => { entity.Property(x => x.Reason).HasMaxLength(500); entity.Property(x => x.ReviewComment).HasMaxLength(500); entity.HasIndex(x => new { x.TeachingTaskId, x.Status }); entity.HasIndex(x => x.ApplicantUserId); entity.HasIndex(x => new { x.Status, x.CreatedAt }); entity.HasIndex(x => new { x.SourceScheduleEntryId, x.SourceWeek }); entity.HasOne(x => x.TeachingTask).WithMany() .HasForeignKey(x => x.TeachingTaskId).OnDelete(DeleteBehavior.Restrict); entity.HasOne(x => x.Classroom).WithMany() .HasForeignKey(x => x.ClassroomId).OnDelete(DeleteBehavior.SetNull); entity.HasOne(x => x.SubstituteTeacher).WithMany() .HasForeignKey(x => x.SubstituteTeacherId).OnDelete(DeleteBehavior.SetNull); }); builder.Entity(entity => { entity.Property(x => x.Title).HasMaxLength(200); entity.Property(x => x.Content).HasColumnType("longtext"); entity.Property(x => x.LinkUrl).HasMaxLength(300); entity.HasIndex(x => new { x.UserId, x.IsRead }); entity.HasIndex(x => new { x.UserId, x.Category, x.CreatedAt }); entity.HasIndex(x => x.MessageDispatchId); entity.HasIndex(x => x.CreatedAt); entity.HasOne(x => x.MessageDispatch) .WithMany(x => x.Notifications) .HasForeignKey(x => x.MessageDispatchId) .OnDelete(DeleteBehavior.Cascade); }); builder.Entity(entity => { entity.Property(x => x.SenderName).HasMaxLength(100); entity.Property(x => x.Title).HasMaxLength(200); entity.Property(x => x.Content).HasColumnType("longtext"); entity.Property(x => x.AudienceName).HasMaxLength(200); entity.Property(x => x.LinkUrl).HasMaxLength(300); entity.HasIndex(x => new { x.SenderUserId, x.CreatedAt }); }); builder.Entity(entity => { entity.Property(x => x.Method).HasMaxLength(10); entity.Property(x => x.Path).HasMaxLength(300); entity.Property(x => x.UserName).HasMaxLength(100); entity.Property(x => x.IpAddress).HasMaxLength(64); entity.HasIndex(x => x.CreatedAt); }); builder.Entity(entity => { entity.Property(x => x.LastError).HasMaxLength(2000); entity.HasIndex(x => new { x.JobKind, x.JobId }).IsUnique(); entity.HasIndex(x => new { x.State, x.CreatedAt }); entity.HasIndex(x => new { x.State, x.CompletedAt }); entity.HasIndex(x => x.LeaseExpiresAt); }); builder.Entity(entity => { entity.Property(x => x.DocumentNumber).HasMaxLength(50); entity.Property(x => x.VerificationCodeHash).HasMaxLength(64); entity.Property(x => x.Purpose).HasMaxLength(200); entity.Property(x => x.SnapshotJson).HasColumnType("longtext"); entity.Property(x => x.PdfContent).HasColumnType("longblob"); entity.Property(x => x.PdfSha256).HasMaxLength(64); entity.Property(x => x.InvalidationReason).HasMaxLength(500); entity.HasIndex(x => x.DocumentNumber).IsUnique(); entity.HasIndex(x => x.VerificationCodeHash).IsUnique(); entity.HasIndex(x => new { x.StudentId, x.IssuedAt }); entity.HasIndex(x => new { x.Status, x.IssuedAt }); entity.HasOne(x => x.Student) .WithMany() .HasForeignKey(x => x.StudentId) .OnDelete(DeleteBehavior.Restrict); entity.HasOne(x => x.IssuedByUser) .WithMany() .HasForeignKey(x => x.IssuedByUserId) .OnDelete(DeleteBehavior.Restrict); entity.HasOne(x => x.InvalidatedByUser) .WithMany() .HasForeignKey(x => x.InvalidatedByUserId) .OnDelete(DeleteBehavior.Restrict); entity.HasIndex(x => x.ReissuedFromDocumentId).IsUnique(); entity.HasOne(x => x.ReissuedFromDocument) .WithMany() .HasForeignKey(x => x.ReissuedFromDocumentId) .OnDelete(DeleteBehavior.Restrict); }); builder.Entity(entity => { entity.Property(x => x.IpAddress).HasMaxLength(64); entity.Property(x => x.UserAgent).HasMaxLength(500); entity.HasIndex(x => new { x.OfficialDocumentId, x.CreatedAt }); entity.HasIndex(x => new { x.DownloadedByUserId, x.CreatedAt }); entity.HasOne(x => x.OfficialDocument) .WithMany(x => x.Downloads) .HasForeignKey(x => x.OfficialDocumentId) .OnDelete(DeleteBehavior.Cascade); entity.HasOne(x => x.DownloadedByUser) .WithMany() .HasForeignKey(x => x.DownloadedByUserId) .OnDelete(DeleteBehavior.Restrict); }); } public override Task SaveChangesAsync(CancellationToken cancellationToken = default) { foreach (var entry in ChangeTracker.Entries() .Where(x => x.State == EntityState.Modified)) { entry.Entity.UpdatedAt = DateTime.UtcNow; } return base.SaveChangesAsync(cancellationToken); } private static void ConfigureCatalog(ModelBuilder builder) where TEntity : CatalogEntity { builder.Entity(entity => { entity.Property(x => x.Code).HasMaxLength(40); entity.Property(x => x.Name).HasMaxLength(100); entity.HasIndex(x => x.Code).IsUnique(); entity.HasIndex(x => new { x.IsEnabled, x.SortOrder }); }); } } public sealed class DateOnlyDateTimeConverter() : ValueConverter( date => date.ToDateTime(TimeOnly.MinValue), value => DateOnly.FromDateTime(value)); public sealed class TimeOnlyTimeSpanConverter() : ValueConverter( time => time.ToTimeSpan(), value => TimeOnly.FromTimeSpan(value));