This commit is contained in:
2026-08-10 19:04:35 +08:00 Unverified
parent 04c693a981
commit 949f389854
@@ -166,6 +166,14 @@ public sealed class AppDbContext(DbContextOptions<AppDbContext> options)
configurationBuilder.Properties<TimeOnly>()
.HaveConversion<TimeOnlyTimeSpanConverter>()
.HaveColumnType("time");
// MySQL DATETIME has no offset or DateTimeKind. All system timestamps
// are persisted as UTC, so restore that contract when materializing
// them. System.Text.Json will then emit the trailing "Z", allowing
// browsers to convert timestamps to the viewer's local time correctly.
configurationBuilder.Properties<DateTime>()
.HaveConversion<UtcDateTimeConverter>()
.HaveColumnType("datetime(6)");
}
protected override void OnModelCreating(ModelBuilder builder)
@@ -1086,6 +1094,10 @@ public sealed class AppDbContext(DbContextOptions<AppDbContext> options)
});
builder.Entity<ExamSession>(entity =>
{
// Exam slot times are China-local wall-clock times, not instants.
// Keep their existing API representation offset-free.
entity.Property(x => x.StartsAt).HasConversion<UnspecifiedDateTimeConverter>();
entity.Property(x => x.EndsAt).HasConversion<UnspecifiedDateTimeConverter>();
entity.Property(x => x.Notes).HasMaxLength(500);
entity.HasIndex(x => new { x.ExamPlanId, x.StartsAt });
entity.HasIndex(x => x.TeachingTaskId);
@@ -1112,6 +1124,8 @@ public sealed class AppDbContext(DbContextOptions<AppDbContext> options)
builder.Entity<ExamRoomAssignment>(entity =>
{
entity.ToTable("ExamRooms");
entity.Property(x => x.StartsAt).HasConversion<UnspecifiedDateTimeConverter>();
entity.Property(x => x.EndsAt).HasConversion<UnspecifiedDateTimeConverter>();
entity.HasIndex(x => new { x.ExamPlanId, x.StartsAt })
.HasDatabaseName("IX_ExamRooms_Plan_Time");
entity.HasIndex(x => new
@@ -1200,6 +1214,9 @@ public sealed class AppDbContext(DbContextOptions<AppDbContext> options)
});
builder.Entity<MakeupExamSession>(entity =>
{
// Makeup-exam slot times follow the same wall-clock convention.
entity.Property(x => x.StartsAt).HasConversion<UnspecifiedDateTimeConverter>();
entity.Property(x => x.EndsAt).HasConversion<UnspecifiedDateTimeConverter>();
entity.Property(x => x.Notes).HasMaxLength(500);
entity.HasIndex(x => new { x.MakeupExamPlanId, x.StartsAt });
entity.HasIndex(x => x.TeachingTaskId);
@@ -1646,3 +1663,15 @@ public sealed class TimeOnlyTimeSpanConverter()
: ValueConverter<TimeOnly, TimeSpan>(
time => time.ToTimeSpan(),
value => TimeOnly.FromTimeSpan(value));
public sealed class UtcDateTimeConverter()
: ValueConverter<DateTime, DateTime>(
value => value.Kind == DateTimeKind.Local
? value.ToUniversalTime()
: DateTime.SpecifyKind(value, DateTimeKind.Utc),
value => DateTime.SpecifyKind(value, DateTimeKind.Utc));
public sealed class UnspecifiedDateTimeConverter()
: ValueConverter<DateTime, DateTime>(
value => DateTime.SpecifyKind(value, DateTimeKind.Unspecified),
value => DateTime.SpecifyKind(value, DateTimeKind.Unspecified));