修复数据

This commit is contained in:
2026-07-25 22:14:40 +08:00 Unverified
parent d42ec74822
commit aafc8c537e
3 changed files with 93 additions and 29 deletions
@@ -4,6 +4,7 @@ using Jiaowu.Api.Domain.Identity;
using Jiaowu.Api.Domain.System;
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
namespace Jiaowu.Api.Infrastructure.Persistence;
@@ -90,6 +91,18 @@ public sealed class AppDbContext(DbContextOptions<AppDbContext> options)
Set<GraduationClearanceRecord>();
public DbSet<AuditLog> AuditLogs => Set<AuditLog>();
protected override void ConfigureConventions(
ModelConfigurationBuilder configurationBuilder)
{
base.ConfigureConventions(configurationBuilder);
// Connector/NET returns MySQL DATE values as DateTime. An explicit
// provider conversion prevents EF from asking the reader for DateOnly.
configurationBuilder.Properties<DateOnly>()
.HaveConversion<DateOnlyDateTimeConverter>()
.HaveColumnType("date");
}
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
@@ -904,3 +917,8 @@ public sealed class AppDbContext(DbContextOptions<AppDbContext> options)
});
}
}
public sealed class DateOnlyDateTimeConverter()
: ValueConverter<DateOnly, DateTime>(
date => date.ToDateTime(TimeOnly.MinValue),
value => DateOnly.FromDateTime(value));
@@ -8,6 +8,8 @@ public sealed class DemoDataSeeder(
AppDbContext db,
ILogger<DemoDataSeeder> logger)
{
// MySql.EntityFrameworkCore 10 cannot type-map parameterized primitive
// collections, so membership in definition/ID sets is checked after loading.
private const int Grade = 2026;
private const int ClassesPerMajor = 2;
private const int StudentsPerClass = 35;
@@ -176,14 +178,18 @@ public sealed class DemoDataSeeder(
.SelectMany(x => x.Majors)
.Select(x => x.Code)
.ToHashSet(StringComparer.OrdinalIgnoreCase);
var majors = await db.Majors
var majors = (await db.Majors
.OrderBy(x => x.Code)
.ToListAsync(cancellationToken))
.Where(x => targetMajorCodes.Contains(x.Code))
.ToList();
var majorIds = majors.Select(x => x.Id).ToHashSet();
var existingClasses = (await db.AdministrativeClasses
.Where(x => x.Grade == Grade)
.OrderBy(x => x.Code)
.ToListAsync(cancellationToken);
var existingClasses = await db.AdministrativeClasses
.Where(x => x.Grade == Grade && targetMajorCodes.Contains(x.Major!.Code))
.OrderBy(x => x.Code)
.ToListAsync(cancellationToken);
.ToListAsync(cancellationToken))
.Where(x => majorIds.Contains(x.MajorId))
.ToList();
var existingCodes = (await db.AdministrativeClasses
.Select(x => x.Code)
.ToListAsync(cancellationToken))
@@ -218,13 +224,16 @@ public sealed class DemoDataSeeder(
var collegeCodes = CollegeDefinitions
.Select(x => x.Code)
.ToHashSet(StringComparer.OrdinalIgnoreCase);
var colleges = await db.Colleges
.Where(x => collegeCodes.Contains(x.Code))
var colleges = (await db.Colleges
.OrderBy(x => x.Code)
.ToListAsync(cancellationToken);
var existingTeachers = await db.Teachers
.Where(x => colleges.Select(c => c.Id).Contains(x.CollegeId))
.ToListAsync(cancellationToken);
.ToListAsync(cancellationToken))
.Where(x => collegeCodes.Contains(x.Code))
.ToList();
var collegeIds = colleges.Select(x => x.Id).ToHashSet();
var existingTeachers = (await db.Teachers
.ToListAsync(cancellationToken))
.Where(x => collegeIds.Contains(x.CollegeId))
.ToList();
var existingNumbers = (await db.Teachers
.Select(x => x.TeacherNumber)
.ToListAsync(cancellationToken))
@@ -264,14 +273,23 @@ public sealed class DemoDataSeeder(
.SelectMany(x => x.Majors)
.Select(x => x.Code)
.ToHashSet(StringComparer.OrdinalIgnoreCase);
var classes = await db.AdministrativeClasses
.Where(x => x.Grade == Grade && targetMajorCodes.Contains(x.Major!.Code))
var targetMajorIds = (await db.Majors
.Select(x => new { x.Id, x.Code })
.ToListAsync(cancellationToken))
.Where(x => targetMajorCodes.Contains(x.Code))
.Select(x => x.Id)
.ToHashSet();
var classes = (await db.AdministrativeClasses
.Where(x => x.Grade == Grade)
.OrderBy(x => x.Code)
.ToListAsync(cancellationToken);
.ToListAsync(cancellationToken))
.Where(x => targetMajorIds.Contains(x.MajorId))
.ToList();
var classIds = classes.Select(x => x.Id).ToHashSet();
var existingStudents = await db.Students
var existingStudents = (await db.Students
.ToListAsync(cancellationToken))
.Where(x => classIds.Contains(x.AdministrativeClassId))
.ToListAsync(cancellationToken);
.ToList();
var existingNumbers = (await db.Students
.Select(x => x.StudentNumber)
.ToListAsync(cancellationToken))
@@ -404,19 +422,23 @@ public sealed class DemoDataSeeder(
var collegeCodes = CollegeDefinitions
.Select(x => x.Code)
.ToHashSet(StringComparer.OrdinalIgnoreCase);
var colleges = await db.Colleges
var colleges = (await db.Colleges
.OrderBy(x => x.Code)
.ToListAsync(cancellationToken))
.Where(x => collegeCodes.Contains(x.Code))
.OrderBy(x => x.Code)
.ToListAsync(cancellationToken);
.ToList();
var collegeIds = colleges.Select(x => x.Id).ToHashSet();
var teachers = await db.Teachers
.Where(x => collegeIds.Contains(x.CollegeId) && x.Status == TeacherStatus.Active)
var teachers = (await db.Teachers
.Where(x => x.Status == TeacherStatus.Active)
.OrderBy(x => x.TeacherNumber)
.ToListAsync(cancellationToken);
var courses = await db.Courses
.ToListAsync(cancellationToken))
.Where(x => collegeIds.Contains(x.CollegeId))
.ToList();
var courses = (await db.Courses
.OrderBy(x => x.Code)
.ToListAsync(cancellationToken);
.ToListAsync(cancellationToken))
.Where(x => collegeIds.Contains(x.CollegeId))
.ToList();
var publicCourses = courses
.Where(x => x.Nature is CourseNature.GeneralRequired or CourseNature.GeneralElective)
.ToList();
+28 -4
View File
@@ -13,10 +13,7 @@ public sealed class MySqlMigrationTests
[Fact]
public void Production_migration_is_discoverable_and_generates_mysql_sql()
{
var options = new DbContextOptionsBuilder<AppDbContext>()
.UseMySQL(
"Server=localhost;Database=jiaowu;User=__test__;Password=__not_used__;")
.Options;
var options = CreateMySqlOptions();
using var db = new AppDbContext(options);
Assert.Contains(LatestMigration, db.Database.GetMigrations());
@@ -33,4 +30,31 @@ public sealed class MySqlMigrationTests
Assert.DoesNotContain("0001-01-01", script);
Assert.DoesNotContain("0000-00-00", script);
}
[Fact]
public void MySql_date_properties_use_datetime_provider_conversion()
{
using var db = new AppDbContext(CreateMySqlOptions());
var dateProperties = db.Model.GetEntityTypes()
.SelectMany(x => x.GetProperties())
.Where(x => x.ClrType == typeof(DateOnly) ||
x.ClrType == typeof(DateOnly?))
.ToList();
Assert.NotEmpty(dateProperties);
Assert.All(dateProperties, property =>
{
Assert.Equal("date", property.GetColumnType());
Assert.Equal(
typeof(DateTime),
property.GetTypeMapping().Converter?.ProviderClrType);
});
}
private static DbContextOptions<AppDbContext> CreateMySqlOptions() =>
new DbContextOptionsBuilder<AppDbContext>()
.UseMySQL(
"Server=localhost;Database=jiaowu;User=__test__;Password=__not_used__;")
.Options;
}