From 2e422acd19cabf0bb38ced61321dda19360cbb74 Mon Sep 17 00:00:00 2001 From: biss Date: Mon, 27 Jul 2026 17:27:17 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8DMySQL=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Exams/ExamArrangementService.cs | 12 +++-- .../Exams/InvigilatorCandidateQuery.cs | 15 ++++++ .../Exams/MakeupExamArrangementService.cs | 12 +++-- .../ExamArrangementServiceTests.cs | 48 +++++++++++++------ tests/Jiaowu.Api.Tests/MySqlMigrationTests.cs | 16 +++++++ 5 files changed, 79 insertions(+), 24 deletions(-) create mode 100644 src/Jiaowu.Api/Infrastructure/Exams/InvigilatorCandidateQuery.cs diff --git a/src/Jiaowu.Api/Infrastructure/Exams/ExamArrangementService.cs b/src/Jiaowu.Api/Infrastructure/Exams/ExamArrangementService.cs index ceba576..2dbd2c2 100644 --- a/src/Jiaowu.Api/Infrastructure/Exams/ExamArrangementService.cs +++ b/src/Jiaowu.Api/Infrastructure/Exams/ExamArrangementService.cs @@ -230,12 +230,14 @@ public sealed class ExamArrangementService(AppDbContext db) foreach (var id in dbBusyIds) busyTeacherIds.Add(id); foreach (var id in excludeTeacherIds) busyTeacherIds.Add(id); - return await db.Teachers.AsNoTracking() - .Where(x => x.Status == TeacherStatus.Active) - .WhereNotIn(busyTeacherIds, x => x.Id) - .OrderBy(x => Guid.NewGuid()) - .Take(needed) + var candidates = await InvigilatorCandidateQuery + .Create(db, busyTeacherIds) .ToListAsync(cancellationToken); + + return candidates + .OrderBy(_ => Random.Shared.Next()) + .Take(needed) + .ToList(); } } diff --git a/src/Jiaowu.Api/Infrastructure/Exams/InvigilatorCandidateQuery.cs b/src/Jiaowu.Api/Infrastructure/Exams/InvigilatorCandidateQuery.cs new file mode 100644 index 0000000..2febabb --- /dev/null +++ b/src/Jiaowu.Api/Infrastructure/Exams/InvigilatorCandidateQuery.cs @@ -0,0 +1,15 @@ +using Jiaowu.Api.Domain.Academic; +using Jiaowu.Api.Infrastructure.Persistence; +using Microsoft.EntityFrameworkCore; + +namespace Jiaowu.Api.Infrastructure.Exams; + +internal static class InvigilatorCandidateQuery +{ + public static IQueryable Create( + AppDbContext db, + IEnumerable excludedTeacherIds) => + db.Teachers.AsNoTracking() + .Where(x => x.Status == TeacherStatus.Active) + .WhereNotIn(excludedTeacherIds, x => x.Id); +} diff --git a/src/Jiaowu.Api/Infrastructure/Exams/MakeupExamArrangementService.cs b/src/Jiaowu.Api/Infrastructure/Exams/MakeupExamArrangementService.cs index ee64af7..a903689 100644 --- a/src/Jiaowu.Api/Infrastructure/Exams/MakeupExamArrangementService.cs +++ b/src/Jiaowu.Api/Infrastructure/Exams/MakeupExamArrangementService.cs @@ -225,11 +225,13 @@ public sealed class MakeupExamArrangementService(AppDbContext db) foreach (var id in dbBusyIds) busyTeacherIds.Add(id); foreach (var id in excludeTeacherIds) busyTeacherIds.Add(id); - return await db.Teachers.AsNoTracking() - .Where(x => x.Status == TeacherStatus.Active) - .WhereNotIn(busyTeacherIds, x => x.Id) - .OrderBy(x => Guid.NewGuid()) - .Take(needed) + var candidates = await InvigilatorCandidateQuery + .Create(db, busyTeacherIds) .ToListAsync(cancellationToken); + + return candidates + .OrderBy(_ => Random.Shared.Next()) + .Take(needed) + .ToList(); } } diff --git a/tests/Jiaowu.Api.Tests/ExamArrangementServiceTests.cs b/tests/Jiaowu.Api.Tests/ExamArrangementServiceTests.cs index 5339b02..28b02bb 100644 --- a/tests/Jiaowu.Api.Tests/ExamArrangementServiceTests.cs +++ b/tests/Jiaowu.Api.Tests/ExamArrangementServiceTests.cs @@ -9,7 +9,7 @@ namespace Jiaowu.Api.Tests; public sealed class ExamArrangementServiceTests { [Fact] - public async Task Normal_arrangement_only_updates_selected_sessions_and_requested_resource() + public async Task Normal_arrangement_only_updates_selected_session() { await using var connection = new SqliteConnection("Data Source=:memory:"); await connection.OpenAsync(); @@ -37,21 +37,27 @@ public sealed class ExamArrangementServiceTests plan.Id, [selected.Id], assignClassrooms: true, - assignInvigilators: false, + assignInvigilators: true, CancellationToken.None); - var persistedSelected = await db.ExamSessions.FindAsync(selected.Id); - var persistedUntouched = await db.ExamSessions.FindAsync(untouched.Id); + var persistedSelected = await db.ExamSessions + .Include(x => x.Invigilators) + .SingleAsync(x => x.Id == selected.Id); + var persistedUntouched = await db.ExamSessions + .Include(x => x.Invigilators) + .SingleAsync(x => x.Id == untouched.Id); Assert.True(result.Success); - Assert.NotNull(persistedSelected!.ClassroomId); - Assert.Null(persistedUntouched!.ClassroomId); - Assert.Empty(persistedSelected.Invigilators); + Assert.NotNull(persistedSelected.ClassroomId); + Assert.Null(persistedUntouched.ClassroomId); + Assert.Single(persistedSelected.Invigilators); + Assert.Empty(persistedUntouched.Invigilators); Assert.Contains("1个场次处理完成", result.Message); + Assert.Contains("1名监考教师", result.Message); Assert.Contains("程序设计", result.Message); } [Fact] - public async Task Makeup_arrangement_only_updates_selected_sessions_and_requested_resource() + public async Task Makeup_arrangement_only_updates_selected_session() { await using var connection = new SqliteConnection("Data Source=:memory:"); await connection.OpenAsync(); @@ -79,16 +85,22 @@ public sealed class ExamArrangementServiceTests plan.Id, [selected.Id], assignClassrooms: true, - assignInvigilators: false, + assignInvigilators: true, CancellationToken.None); - var persistedSelected = await db.MakeupExamSessions.FindAsync(selected.Id); - var persistedUntouched = await db.MakeupExamSessions.FindAsync(untouched.Id); + var persistedSelected = await db.MakeupExamSessions + .Include(x => x.Invigilators) + .SingleAsync(x => x.Id == selected.Id); + var persistedUntouched = await db.MakeupExamSessions + .Include(x => x.Invigilators) + .SingleAsync(x => x.Id == untouched.Id); Assert.True(result.Success); - Assert.NotNull(persistedSelected!.ClassroomId); - Assert.Null(persistedUntouched!.ClassroomId); - Assert.Empty(persistedSelected.Invigilators); + Assert.NotNull(persistedSelected.ClassroomId); + Assert.Null(persistedUntouched.ClassroomId); + Assert.Single(persistedSelected.Invigilators); + Assert.Empty(persistedUntouched.Invigilators); Assert.Contains("1个场次处理完成", result.Message); + Assert.Contains("1名监考教师", result.Message); Assert.Contains("程序设计", result.Message); } @@ -175,6 +187,13 @@ public sealed class ExamArrangementServiceTests CourseId = secondCourse.Id, Status = TeachingTaskStatus.Published }; + var invigilator = new Teacher + { + TeacherNumber = $"INVIGILATOR-{suffix}", + Name = "监考教师", + CollegeId = college.Id, + Status = TeacherStatus.Active + }; db.AddRange( term, @@ -186,6 +205,7 @@ public sealed class ExamArrangementServiceTests secondCourse, firstTask, secondTask, + invigilator, new ScheduleTimeSlot { AcademicTermId = term.Id, diff --git a/tests/Jiaowu.Api.Tests/MySqlMigrationTests.cs b/tests/Jiaowu.Api.Tests/MySqlMigrationTests.cs index 526be8e..36e6814 100644 --- a/tests/Jiaowu.Api.Tests/MySqlMigrationTests.cs +++ b/tests/Jiaowu.Api.Tests/MySqlMigrationTests.cs @@ -1,4 +1,5 @@ using Jiaowu.Api.Infrastructure.Persistence; +using Jiaowu.Api.Infrastructure.Exams; using Jiaowu.Api.Infrastructure.Teaching; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; @@ -99,6 +100,21 @@ public sealed class MySqlMigrationTests Assert.Contains(ids[1].ToString(), sql, StringComparison.OrdinalIgnoreCase); } + [Fact] + public void MySql_invigilator_candidate_query_is_translatable_without_uuid_ordering() + { + using var db = new AppDbContext(CreateMySqlOptions()); + var excludedTeacherId = + Guid.Parse("11111111-1111-1111-1111-111111111111"); + + var sql = InvigilatorCandidateQuery + .Create(db, [excludedTeacherId]) + .ToQueryString(); + + Assert.Contains("Teachers", sql, StringComparison.OrdinalIgnoreCase); + Assert.DoesNotContain("UUID(", sql, StringComparison.OrdinalIgnoreCase); + } + [Fact] public void MySql_teaching_task_roster_query_is_translatable() {