考试排考

This commit is contained in:
2026-07-25 14:51:17 +08:00 Unverified
parent 36049af692
commit 4e7da6709e
12 changed files with 1306 additions and 118 deletions
@@ -564,13 +564,16 @@ public sealed class AppDbContext(DbContextOptions<AppDbContext> options)
entity.Property(x => x.Notes).HasMaxLength(500);
entity.HasIndex(x => new { x.ExamPlanId, x.StartsAt });
entity.HasIndex(x => x.TeachingTaskId);
entity.HasIndex(x => x.ClassroomId);
entity.HasIndex(x => new { x.ExamPlanId, x.ExamDate });
entity.HasIndex(x => x.RequiredBuildingId);
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.Restrict);
.HasForeignKey(x => x.ClassroomId).OnDelete(DeleteBehavior.SetNull);
entity.HasOne(x => x.RequiredBuilding).WithMany()
.HasForeignKey(x => x.RequiredBuildingId).OnDelete(DeleteBehavior.SetNull);
});
builder.Entity<ExamSessionInvigilator>(entity =>
{
@@ -583,8 +583,12 @@ public sealed class DatabaseInitializer(
{
TeachingTaskId = task.Id,
ClassroomId = room.Id,
ExamDate = new DateOnly(2027, 1, 8),
StartPeriod = 1,
PeriodCount = 2,
StartsAt = new DateTime(2027, 1, 8, 9, 0, 0, DateTimeKind.Utc),
EndsAt = new DateTime(2027, 1, 8, 11, 0, 0, DateTimeKind.Utc),
RequiredInvigilatorCount = 2,
Invigilators =
[
new ExamSessionInvigilator { TeacherId = teacher.Id }
@@ -32,6 +32,8 @@ public sealed class DevelopmentSqliteMigrator(
"20260725_18_schedule_publish_jobs";
private const string FlexibleGradesMigration =
"20260725_19_flexible_grades";
private const string ExamSchedulingOptimizationMigration =
"20260725_20_exam_scheduling_optimization";
public async Task MigrateAsync(CancellationToken cancellationToken = default)
{
@@ -208,6 +210,21 @@ public sealed class DevelopmentSqliteMigrator(
AttendanceMigration,
attendanceSheetsExist ? [] : AttendanceStatements,
cancellationToken);
var examSchedulingOptimizationExists = await db.Database
.SqlQueryRaw<int>(
"""
SELECT COUNT(*) AS "Value"
FROM pragma_table_info('ExamSessions')
WHERE name = 'ExamDate'
""")
.AnyAsync(value => value > 0, cancellationToken);
await ApplyMigrationAsync(
ExamSchedulingOptimizationMigration,
examSchedulingOptimizationExists
? []
: ExamSchedulingOptimizationStatements,
cancellationToken);
}
private async Task ApplyMigrationAsync(
@@ -1367,4 +1384,81 @@ public sealed class DevelopmentSqliteMigrator(
ON "SchedulePublishJobs" ("RequestedByUserId");
"""
];
private static readonly string[] ExamSchedulingOptimizationStatements =
[
"""
ALTER TABLE "ExamSessions" ADD COLUMN "ExamDate" TEXT NOT NULL DEFAULT '2027-01-01';
""",
"""
ALTER TABLE "ExamSessions" ADD COLUMN "StartPeriod" INTEGER NOT NULL DEFAULT 1;
""",
"""
ALTER TABLE "ExamSessions" ADD COLUMN "PeriodCount" INTEGER NOT NULL DEFAULT 2;
""",
"""
ALTER TABLE "ExamSessions" ADD COLUMN "RequiredBuildingId" TEXT NULL;
""",
"""
ALTER TABLE "ExamSessions" ADD COLUMN "RequiredInvigilatorCount" INTEGER NOT NULL DEFAULT 2;
""",
"""
CREATE TABLE "ExamSessions_Temp" (
"Id" TEXT NOT NULL CONSTRAINT "PK_ExamSessions" PRIMARY KEY,
"ExamPlanId" TEXT NOT NULL,
"TeachingTaskId" TEXT NOT NULL,
"ClassroomId" TEXT NULL,
"ExamDate" TEXT NOT NULL,
"StartPeriod" INTEGER NOT NULL,
"PeriodCount" INTEGER NOT NULL,
"StartsAt" TEXT NOT NULL,
"EndsAt" TEXT NOT NULL,
"RequiredBuildingId" TEXT NULL,
"RequiredInvigilatorCount" INTEGER NOT NULL,
"Notes" TEXT NULL,
"CreatedAt" TEXT NOT NULL,
"UpdatedAt" TEXT NOT NULL,
CONSTRAINT "FK_ExamSessions_ExamPlans_ExamPlanId"
FOREIGN KEY ("ExamPlanId") REFERENCES "ExamPlans" ("Id") ON DELETE CASCADE,
CONSTRAINT "FK_ExamSessions_TeachingTasks_TeachingTaskId"
FOREIGN KEY ("TeachingTaskId") REFERENCES "TeachingTasks" ("Id") ON DELETE RESTRICT,
CONSTRAINT "FK_ExamSessions_Classrooms_ClassroomId"
FOREIGN KEY ("ClassroomId") REFERENCES "Classrooms" ("Id") ON DELETE SET NULL,
CONSTRAINT "FK_ExamSessions_Buildings_RequiredBuildingId"
FOREIGN KEY ("RequiredBuildingId") REFERENCES "Buildings" ("Id") ON DELETE SET NULL
);
""",
"""
INSERT INTO "ExamSessions_Temp"
("Id","ExamPlanId","TeachingTaskId","ClassroomId","ExamDate",
"StartPeriod","PeriodCount","StartsAt","EndsAt",
"RequiredBuildingId","RequiredInvigilatorCount",
"Notes","CreatedAt","UpdatedAt")
SELECT "Id","ExamPlanId","TeachingTaskId","ClassroomId",
'2027-01-01',
1,2,
"StartsAt","EndsAt",
NULL,2,
"Notes","CreatedAt","UpdatedAt"
FROM "ExamSessions";
""",
"DROP TABLE \"ExamSessions\";",
"ALTER TABLE \"ExamSessions_Temp\" RENAME TO \"ExamSessions\";",
"""
CREATE INDEX "IX_ExamSessions_ExamPlanId_StartsAt"
ON "ExamSessions" ("ExamPlanId", "StartsAt");
""",
"""
CREATE INDEX "IX_ExamSessions_TeachingTaskId"
ON "ExamSessions" ("TeachingTaskId");
""",
"""
CREATE INDEX "IX_ExamSessions_ExamPlanId_ExamDate"
ON "ExamSessions" ("ExamPlanId", "ExamDate");
""",
"""
CREATE INDEX "IX_ExamSessions_RequiredBuildingId"
ON "ExamSessions" ("RequiredBuildingId");
"""
];
}
@@ -0,0 +1,158 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Jiaowu.Api.Infrastructure.Persistence.Migrations.MySql
{
/// <inheritdoc />
public partial class ExamSchedulingOptimization : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
// Drop old FK/index on ClassroomId
migrationBuilder.DropForeignKey(
name: "FK_ExamSessions_Classrooms_ClassroomId",
table: "ExamSessions");
migrationBuilder.DropIndex(
name: "IX_ExamSessions_ClassroomId",
table: "ExamSessions");
// Make ClassroomId nullable
migrationBuilder.AlterColumn<Guid>(
name: "ClassroomId",
table: "ExamSessions",
type: "char(36)",
nullable: true,
oldClrType: typeof(Guid),
oldType: "char(36)");
// Add new columns
migrationBuilder.AddColumn<DateOnly>(
name: "ExamDate",
table: "ExamSessions",
type: "date",
nullable: false,
defaultValue: new DateOnly(2027, 1, 1));
migrationBuilder.AddColumn<int>(
name: "StartPeriod",
table: "ExamSessions",
type: "int",
nullable: false,
defaultValue: 1);
migrationBuilder.AddColumn<int>(
name: "PeriodCount",
table: "ExamSessions",
type: "int",
nullable: false,
defaultValue: 2);
migrationBuilder.AddColumn<Guid>(
name: "RequiredBuildingId",
table: "ExamSessions",
type: "char(36)",
nullable: true);
migrationBuilder.AddColumn<int>(
name: "RequiredInvigilatorCount",
table: "ExamSessions",
type: "int",
nullable: false,
defaultValue: 2);
// Re-add FK/index on ClassroomId (nullable, SetNull)
migrationBuilder.CreateIndex(
name: "IX_ExamSessions_ClassroomId",
table: "ExamSessions",
column: "ClassroomId");
migrationBuilder.AddForeignKey(
name: "FK_ExamSessions_Classrooms_ClassroomId",
table: "ExamSessions",
column: "ClassroomId",
principalTable: "Classrooms",
principalColumn: "Id",
onDelete: ReferentialAction.SetNull);
// New indices
migrationBuilder.CreateIndex(
name: "IX_ExamSessions_ExamPlanId_ExamDate",
table: "ExamSessions",
columns: new[] { "ExamPlanId", "ExamDate" });
migrationBuilder.CreateIndex(
name: "IX_ExamSessions_RequiredBuildingId",
table: "ExamSessions",
column: "RequiredBuildingId");
migrationBuilder.AddForeignKey(
name: "FK_ExamSessions_Buildings_RequiredBuildingId",
table: "ExamSessions",
column: "RequiredBuildingId",
principalTable: "Buildings",
principalColumn: "Id",
onDelete: ReferentialAction.SetNull);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
// Remove new FK/index
migrationBuilder.DropForeignKey(
name: "FK_ExamSessions_Buildings_RequiredBuildingId",
table: "ExamSessions");
migrationBuilder.DropForeignKey(
name: "FK_ExamSessions_Classrooms_ClassroomId",
table: "ExamSessions");
migrationBuilder.DropIndex(
name: "IX_ExamSessions_ClassroomId",
table: "ExamSessions");
migrationBuilder.DropIndex(
name: "IX_ExamSessions_ExamPlanId_ExamDate",
table: "ExamSessions");
migrationBuilder.DropIndex(
name: "IX_ExamSessions_RequiredBuildingId",
table: "ExamSessions");
// Drop new columns
migrationBuilder.DropColumn(name: "RequiredInvigilatorCount", table: "ExamSessions");
migrationBuilder.DropColumn(name: "RequiredBuildingId", table: "ExamSessions");
migrationBuilder.DropColumn(name: "PeriodCount", table: "ExamSessions");
migrationBuilder.DropColumn(name: "StartPeriod", table: "ExamSessions");
migrationBuilder.DropColumn(name: "ExamDate", table: "ExamSessions");
// Restore ClassroomId to non-nullable
migrationBuilder.AlterColumn<Guid>(
name: "ClassroomId",
table: "ExamSessions",
type: "char(36)",
nullable: false,
defaultValue: Guid.Empty,
oldClrType: typeof(Guid),
oldType: "char(36)",
oldNullable: true);
// Restore original FK/index
migrationBuilder.CreateIndex(
name: "IX_ExamSessions_ClassroomId",
table: "ExamSessions",
column: "ClassroomId");
migrationBuilder.AddForeignKey(
name: "FK_ExamSessions_Classrooms_ClassroomId",
table: "ExamSessions",
column: "ClassroomId",
principalTable: "Classrooms",
principalColumn: "Id",
onDelete: ReferentialAction.Restrict);
}
}
}