发布课表投影改为数据库端删除旧数据、每 2,000 条分批写入并及时释放 EF 跟踪对象,避免大规模发布时内存持续增长。

为“空闲教室/预约占用”新增 学期 + 周次 + 星期 + 节次 + 场地 查询索引;新发布课表的该查询已优先走课次明细投影。
This commit is contained in:
2026-08-09 16:05:57 +08:00 Unverified
parent e39ab4579f
commit 8ef7fc9f9a
5 changed files with 6796 additions and 3 deletions
@@ -543,6 +543,14 @@ public sealed class AppDbContext(DbContextOptions<AppDbContext> options)
builder.Entity<PublishedScheduleOccurrence>(entity =>
{
entity.HasIndex(x => new { x.AcademicTermId, x.TeachingTaskId, x.Week });
entity.HasIndex(x => new
{
x.AcademicTermId,
x.Week,
x.DayOfWeek,
x.StartPeriod,
x.ClassroomId
});
entity.HasIndex(x => new { x.SchedulePlanId, x.ClassroomId, x.Week, x.DayOfWeek, x.StartPeriod });
entity.HasIndex(x => new { x.ScheduleEntryId, x.Week }).IsUnique();
entity.HasOne(x => x.ScheduleEntry).WithMany().HasForeignKey(x => x.ScheduleEntryId).OnDelete(DeleteBehavior.Cascade);
@@ -0,0 +1,27 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Jiaowu.Api.Infrastructure.Persistence.Migrations.MySql
{
/// <inheritdoc />
public partial class OptimizePublishedTimetableOccurrenceLookup : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateIndex(
name: "IX_PublishedScheduleOccurrences_AcademicTermId_Week_DayOfWeek_S~",
table: "PublishedScheduleOccurrences",
columns: new[] { "AcademicTermId", "Week", "DayOfWeek", "StartPeriod", "ClassroomId" });
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropIndex(
name: "IX_PublishedScheduleOccurrences_AcademicTermId_Week_DayOfWeek_S~",
table: "PublishedScheduleOccurrences");
}
}
}
@@ -3552,6 +3552,8 @@ namespace Jiaowu.Api.Infrastructure.Persistence.Migrations.MySql
b.HasIndex("AcademicTermId", "TeachingTaskId", "Week");
b.HasIndex("AcademicTermId", "Week", "DayOfWeek", "StartPeriod", "ClassroomId");
b.HasIndex("SchedulePlanId", "ClassroomId", "Week", "DayOfWeek", "StartPeriod");
b.ToTable("PublishedScheduleOccurrences");
@@ -6,6 +6,7 @@ namespace Jiaowu.Api.Infrastructure.Timetables;
public sealed class PublishedTimetableProjectionService(AppDbContext db)
{
private const int WriteBatchSize = 2_000;
public async Task RebuildPublishedPlansForTaskAsync(Guid teachingTaskId, CancellationToken cancellationToken)
{
var plans = await db.SchedulePlans
@@ -19,10 +20,11 @@ public sealed class PublishedTimetableProjectionService(AppDbContext db)
public async Task RebuildAsync(SchedulePlan plan, CancellationToken cancellationToken)
{
var previous = db.PublishedScheduleOccurrences.Where(x => x.SchedulePlanId == plan.Id);
db.PublishedScheduleOccurrences.RemoveRange(previous);
await db.PublishedScheduleOccurrences
.Where(x => x.SchedulePlanId == plan.Id)
.ExecuteDeleteAsync(cancellationToken);
var rows = new List<PublishedScheduleOccurrence>();
var rows = new List<PublishedScheduleOccurrence>(WriteBatchSize);
foreach (var entry in plan.Entries)
for (var week = entry.StartWeek; week <= entry.EndWeek; week++)
{
@@ -41,8 +43,21 @@ public sealed class PublishedTimetableProjectionService(AppDbContext db)
PeriodCount = entry.PeriodCount,
Kind = entry.Kind
});
if (rows.Count == WriteBatchSize)
await WriteBatchAsync(rows, cancellationToken);
}
if (rows.Count > 0)
await WriteBatchAsync(rows, cancellationToken);
}
private async Task WriteBatchAsync(
List<PublishedScheduleOccurrence> rows,
CancellationToken cancellationToken)
{
db.PublishedScheduleOccurrences.AddRange(rows);
await db.SaveChangesAsync(cancellationToken);
foreach (var row in rows)
db.Entry(row).State = EntityState.Detached;
rows.Clear();
}
}