培养方案:专业/年级版本、课程模块、学分校验、复制版本、发布锁定、旧版本归档。

教学任务:学期开课、授课教师、唯一主讲、合班、容量与周次校验、发布及结课。
SQLite 开发库已自动升级,无需删除原数据库。
新增对应 MySQL EF Core 迁移。
Vue 已编译进 wwwroot,仍可只运行 ASP.NET Core 单服务。
桌面端及 390px 窄屏页面验证通过,无前端控制台错误。
This commit is contained in:
2026-07-24 14:10:43 +08:00 Unverified
parent 30d793773c
commit 628944f64d
23 changed files with 5430 additions and 47 deletions
@@ -7,6 +7,8 @@ public sealed class DevelopmentSqliteMigrator(
ILogger<DevelopmentSqliteMigrator> logger)
{
private const string PeopleAndCoursesMigration = "20260724_01_people_courses";
private const string CurriculumPlansMigration = "20260724_02_curriculum_plans";
private const string TeachingTasksMigration = "20260724_03_teaching_tasks";
public async Task MigrateAsync(CancellationToken cancellationToken = default)
{
@@ -24,12 +26,31 @@ public sealed class DevelopmentSqliteMigrator(
""",
cancellationToken);
await ApplyMigrationAsync(
PeopleAndCoursesMigration,
PeopleAndCoursesStatements,
cancellationToken);
await ApplyMigrationAsync(
CurriculumPlansMigration,
CurriculumPlansStatements,
cancellationToken);
await ApplyMigrationAsync(
TeachingTasksMigration,
TeachingTasksStatements,
cancellationToken);
}
private async Task ApplyMigrationAsync(
string migrationId,
IEnumerable<string> statements,
CancellationToken cancellationToken)
{
var applied = await db.Database
.SqlQuery<string>(
$"""
SELECT "MigrationId" AS "Value"
FROM "__DevelopmentSchemaHistory"
WHERE "MigrationId" = {PeopleAndCoursesMigration}
WHERE "MigrationId" = {migrationId}
""")
.AnyAsync(cancellationToken);
if (applied)
@@ -38,7 +59,7 @@ public sealed class DevelopmentSqliteMigrator(
}
await using var transaction = await db.Database.BeginTransactionAsync(cancellationToken);
foreach (var statement in PeopleAndCoursesStatements)
foreach (var statement in statements)
{
await db.Database.ExecuteSqlRawAsync(statement, cancellationToken);
}
@@ -46,13 +67,13 @@ public sealed class DevelopmentSqliteMigrator(
await db.Database.ExecuteSqlInterpolatedAsync(
$"""
INSERT INTO "__DevelopmentSchemaHistory" ("MigrationId", "AppliedAt")
VALUES ({PeopleAndCoursesMigration}, {DateTime.UtcNow});
VALUES ({migrationId}, {DateTime.UtcNow});
""",
cancellationToken);
await transaction.CommitAsync(cancellationToken);
logger.LogInformation(
"Applied SQLite development schema migration {MigrationId}.",
PeopleAndCoursesMigration);
migrationId);
}
private static readonly string[] PeopleAndCoursesStatements =
@@ -166,4 +187,149 @@ public sealed class DevelopmentSqliteMigrator(
ON "Courses" ("CollegeId", "Nature");
"""
];
private static readonly string[] CurriculumPlansStatements =
[
"""
CREATE TABLE IF NOT EXISTS "CurriculumPlans" (
"Id" TEXT NOT NULL CONSTRAINT "PK_CurriculumPlans" PRIMARY KEY,
"MajorId" TEXT NOT NULL,
"Name" TEXT NOT NULL,
"Version" TEXT NOT NULL,
"EffectiveGrade" INTEGER NOT NULL,
"TotalCredits" TEXT NOT NULL,
"Status" INTEGER NOT NULL,
"Description" TEXT NULL,
"PublishedAt" TEXT NULL,
"CreatedAt" TEXT NOT NULL,
"UpdatedAt" TEXT NOT NULL,
CONSTRAINT "FK_CurriculumPlans_Majors_MajorId"
FOREIGN KEY ("MajorId") REFERENCES "Majors" ("Id") ON DELETE RESTRICT
);
""",
"""
CREATE UNIQUE INDEX IF NOT EXISTS "IX_CurriculumPlans_MajorId_EffectiveGrade_Version"
ON "CurriculumPlans" ("MajorId", "EffectiveGrade", "Version");
""",
"""
CREATE INDEX IF NOT EXISTS "IX_CurriculumPlans_Status_EffectiveGrade"
ON "CurriculumPlans" ("Status", "EffectiveGrade");
""",
"""
CREATE TABLE IF NOT EXISTS "CurriculumModules" (
"Id" TEXT NOT NULL CONSTRAINT "PK_CurriculumModules" PRIMARY KEY,
"CurriculumPlanId" TEXT NOT NULL,
"Code" TEXT NOT NULL,
"Name" TEXT NOT NULL,
"RequiredCredits" TEXT NOT NULL,
"SortOrder" INTEGER NOT NULL,
"CreatedAt" TEXT NOT NULL,
"UpdatedAt" TEXT NOT NULL,
CONSTRAINT "FK_CurriculumModules_CurriculumPlans_CurriculumPlanId"
FOREIGN KEY ("CurriculumPlanId") REFERENCES "CurriculumPlans" ("Id")
ON DELETE CASCADE
);
""",
"""
CREATE UNIQUE INDEX IF NOT EXISTS "IX_CurriculumModules_CurriculumPlanId_Code"
ON "CurriculumModules" ("CurriculumPlanId", "Code");
""",
"""
CREATE TABLE IF NOT EXISTS "CurriculumCourses" (
"Id" TEXT NOT NULL CONSTRAINT "PK_CurriculumCourses" PRIMARY KEY,
"CurriculumModuleId" TEXT NOT NULL,
"CourseId" TEXT NOT NULL,
"RecommendedSemester" INTEGER NOT NULL,
"Type" INTEGER NOT NULL,
"Notes" TEXT NULL,
"CreatedAt" TEXT NOT NULL,
"UpdatedAt" TEXT NOT NULL,
CONSTRAINT "FK_CurriculumCourses_CurriculumModules_CurriculumModuleId"
FOREIGN KEY ("CurriculumModuleId") REFERENCES "CurriculumModules" ("Id")
ON DELETE CASCADE,
CONSTRAINT "FK_CurriculumCourses_Courses_CourseId"
FOREIGN KEY ("CourseId") REFERENCES "Courses" ("Id") ON DELETE RESTRICT
);
""",
"""
CREATE UNIQUE INDEX IF NOT EXISTS "IX_CurriculumCourses_CurriculumModuleId_CourseId"
ON "CurriculumCourses" ("CurriculumModuleId", "CourseId");
""",
"""
CREATE INDEX IF NOT EXISTS "IX_CurriculumCourses_CourseId"
ON "CurriculumCourses" ("CourseId");
"""
];
private static readonly string[] TeachingTasksStatements =
[
"""
CREATE TABLE IF NOT EXISTS "TeachingTasks" (
"Id" TEXT NOT NULL CONSTRAINT "PK_TeachingTasks" PRIMARY KEY,
"TaskNumber" TEXT NOT NULL,
"Name" TEXT NOT NULL,
"AcademicTermId" TEXT NOT NULL,
"CourseId" TEXT NOT NULL,
"Capacity" INTEGER NOT NULL,
"StartWeek" INTEGER NOT NULL,
"EndWeek" INTEGER NOT NULL,
"WeeklyHours" INTEGER NOT NULL,
"Status" INTEGER NOT NULL,
"Notes" TEXT NULL,
"PublishedAt" TEXT NULL,
"CreatedAt" TEXT NOT NULL,
"UpdatedAt" TEXT NOT NULL,
CONSTRAINT "FK_TeachingTasks_AcademicTerms_AcademicTermId"
FOREIGN KEY ("AcademicTermId") REFERENCES "AcademicTerms" ("Id") ON DELETE RESTRICT,
CONSTRAINT "FK_TeachingTasks_Courses_CourseId"
FOREIGN KEY ("CourseId") REFERENCES "Courses" ("Id") ON DELETE RESTRICT
);
""",
"""
CREATE UNIQUE INDEX IF NOT EXISTS "IX_TeachingTasks_TaskNumber"
ON "TeachingTasks" ("TaskNumber");
""",
"""
CREATE INDEX IF NOT EXISTS "IX_TeachingTasks_AcademicTermId_Status"
ON "TeachingTasks" ("AcademicTermId", "Status");
""",
"""
CREATE INDEX IF NOT EXISTS "IX_TeachingTasks_CourseId"
ON "TeachingTasks" ("CourseId");
""",
"""
CREATE TABLE IF NOT EXISTS "TeachingTaskTeachers" (
"TeachingTaskId" TEXT NOT NULL,
"TeacherId" TEXT NOT NULL,
"IsPrimary" INTEGER NOT NULL,
CONSTRAINT "PK_TeachingTaskTeachers"
PRIMARY KEY ("TeachingTaskId", "TeacherId"),
CONSTRAINT "FK_TeachingTaskTeachers_TeachingTasks_TeachingTaskId"
FOREIGN KEY ("TeachingTaskId") REFERENCES "TeachingTasks" ("Id") ON DELETE CASCADE,
CONSTRAINT "FK_TeachingTaskTeachers_Teachers_TeacherId"
FOREIGN KEY ("TeacherId") REFERENCES "Teachers" ("Id") ON DELETE RESTRICT
);
""",
"""
CREATE INDEX IF NOT EXISTS "IX_TeachingTaskTeachers_TeacherId"
ON "TeachingTaskTeachers" ("TeacherId");
""",
"""
CREATE TABLE IF NOT EXISTS "TeachingTaskClasses" (
"TeachingTaskId" TEXT NOT NULL,
"AdministrativeClassId" TEXT NOT NULL,
CONSTRAINT "PK_TeachingTaskClasses"
PRIMARY KEY ("TeachingTaskId", "AdministrativeClassId"),
CONSTRAINT "FK_TeachingTaskClasses_TeachingTasks_TeachingTaskId"
FOREIGN KEY ("TeachingTaskId") REFERENCES "TeachingTasks" ("Id") ON DELETE CASCADE,
CONSTRAINT "FK_TeachingTaskClasses_AdministrativeClasses_AdministrativeClassId"
FOREIGN KEY ("AdministrativeClassId") REFERENCES "AdministrativeClasses" ("Id")
ON DELETE RESTRICT
);
""",
"""
CREATE INDEX IF NOT EXISTS "IX_TeachingTaskClasses_AdministrativeClassId"
ON "TeachingTaskClasses" ("AdministrativeClassId");
"""
];
}