修复数据
This commit is contained in:
@@ -4,6 +4,7 @@ using Jiaowu.Api.Domain.Identity;
|
|||||||
using Jiaowu.Api.Domain.System;
|
using Jiaowu.Api.Domain.System;
|
||||||
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
|
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||||
|
|
||||||
namespace Jiaowu.Api.Infrastructure.Persistence;
|
namespace Jiaowu.Api.Infrastructure.Persistence;
|
||||||
|
|
||||||
@@ -90,6 +91,18 @@ public sealed class AppDbContext(DbContextOptions<AppDbContext> options)
|
|||||||
Set<GraduationClearanceRecord>();
|
Set<GraduationClearanceRecord>();
|
||||||
public DbSet<AuditLog> AuditLogs => Set<AuditLog>();
|
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)
|
protected override void OnModelCreating(ModelBuilder builder)
|
||||||
{
|
{
|
||||||
base.OnModelCreating(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,
|
AppDbContext db,
|
||||||
ILogger<DemoDataSeeder> logger)
|
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 Grade = 2026;
|
||||||
private const int ClassesPerMajor = 2;
|
private const int ClassesPerMajor = 2;
|
||||||
private const int StudentsPerClass = 35;
|
private const int StudentsPerClass = 35;
|
||||||
@@ -176,14 +178,18 @@ public sealed class DemoDataSeeder(
|
|||||||
.SelectMany(x => x.Majors)
|
.SelectMany(x => x.Majors)
|
||||||
.Select(x => x.Code)
|
.Select(x => x.Code)
|
||||||
.ToHashSet(StringComparer.OrdinalIgnoreCase);
|
.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))
|
.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)
|
.OrderBy(x => x.Code)
|
||||||
.ToListAsync(cancellationToken);
|
.ToListAsync(cancellationToken))
|
||||||
var existingClasses = await db.AdministrativeClasses
|
.Where(x => majorIds.Contains(x.MajorId))
|
||||||
.Where(x => x.Grade == Grade && targetMajorCodes.Contains(x.Major!.Code))
|
.ToList();
|
||||||
.OrderBy(x => x.Code)
|
|
||||||
.ToListAsync(cancellationToken);
|
|
||||||
var existingCodes = (await db.AdministrativeClasses
|
var existingCodes = (await db.AdministrativeClasses
|
||||||
.Select(x => x.Code)
|
.Select(x => x.Code)
|
||||||
.ToListAsync(cancellationToken))
|
.ToListAsync(cancellationToken))
|
||||||
@@ -218,13 +224,16 @@ public sealed class DemoDataSeeder(
|
|||||||
var collegeCodes = CollegeDefinitions
|
var collegeCodes = CollegeDefinitions
|
||||||
.Select(x => x.Code)
|
.Select(x => x.Code)
|
||||||
.ToHashSet(StringComparer.OrdinalIgnoreCase);
|
.ToHashSet(StringComparer.OrdinalIgnoreCase);
|
||||||
var colleges = await db.Colleges
|
var colleges = (await db.Colleges
|
||||||
.Where(x => collegeCodes.Contains(x.Code))
|
|
||||||
.OrderBy(x => x.Code)
|
.OrderBy(x => x.Code)
|
||||||
.ToListAsync(cancellationToken);
|
.ToListAsync(cancellationToken))
|
||||||
var existingTeachers = await db.Teachers
|
.Where(x => collegeCodes.Contains(x.Code))
|
||||||
.Where(x => colleges.Select(c => c.Id).Contains(x.CollegeId))
|
.ToList();
|
||||||
.ToListAsync(cancellationToken);
|
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
|
var existingNumbers = (await db.Teachers
|
||||||
.Select(x => x.TeacherNumber)
|
.Select(x => x.TeacherNumber)
|
||||||
.ToListAsync(cancellationToken))
|
.ToListAsync(cancellationToken))
|
||||||
@@ -264,14 +273,23 @@ public sealed class DemoDataSeeder(
|
|||||||
.SelectMany(x => x.Majors)
|
.SelectMany(x => x.Majors)
|
||||||
.Select(x => x.Code)
|
.Select(x => x.Code)
|
||||||
.ToHashSet(StringComparer.OrdinalIgnoreCase);
|
.ToHashSet(StringComparer.OrdinalIgnoreCase);
|
||||||
var classes = await db.AdministrativeClasses
|
var targetMajorIds = (await db.Majors
|
||||||
.Where(x => x.Grade == Grade && targetMajorCodes.Contains(x.Major!.Code))
|
.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)
|
.OrderBy(x => x.Code)
|
||||||
.ToListAsync(cancellationToken);
|
.ToListAsync(cancellationToken))
|
||||||
|
.Where(x => targetMajorIds.Contains(x.MajorId))
|
||||||
|
.ToList();
|
||||||
var classIds = classes.Select(x => x.Id).ToHashSet();
|
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))
|
.Where(x => classIds.Contains(x.AdministrativeClassId))
|
||||||
.ToListAsync(cancellationToken);
|
.ToList();
|
||||||
var existingNumbers = (await db.Students
|
var existingNumbers = (await db.Students
|
||||||
.Select(x => x.StudentNumber)
|
.Select(x => x.StudentNumber)
|
||||||
.ToListAsync(cancellationToken))
|
.ToListAsync(cancellationToken))
|
||||||
@@ -404,19 +422,23 @@ public sealed class DemoDataSeeder(
|
|||||||
var collegeCodes = CollegeDefinitions
|
var collegeCodes = CollegeDefinitions
|
||||||
.Select(x => x.Code)
|
.Select(x => x.Code)
|
||||||
.ToHashSet(StringComparer.OrdinalIgnoreCase);
|
.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))
|
.Where(x => collegeCodes.Contains(x.Code))
|
||||||
.OrderBy(x => x.Code)
|
.ToList();
|
||||||
.ToListAsync(cancellationToken);
|
|
||||||
var collegeIds = colleges.Select(x => x.Id).ToHashSet();
|
var collegeIds = colleges.Select(x => x.Id).ToHashSet();
|
||||||
var teachers = await db.Teachers
|
var teachers = (await db.Teachers
|
||||||
.Where(x => collegeIds.Contains(x.CollegeId) && x.Status == TeacherStatus.Active)
|
.Where(x => x.Status == TeacherStatus.Active)
|
||||||
.OrderBy(x => x.TeacherNumber)
|
.OrderBy(x => x.TeacherNumber)
|
||||||
.ToListAsync(cancellationToken);
|
.ToListAsync(cancellationToken))
|
||||||
var courses = await db.Courses
|
|
||||||
.Where(x => collegeIds.Contains(x.CollegeId))
|
.Where(x => collegeIds.Contains(x.CollegeId))
|
||||||
|
.ToList();
|
||||||
|
var courses = (await db.Courses
|
||||||
.OrderBy(x => x.Code)
|
.OrderBy(x => x.Code)
|
||||||
.ToListAsync(cancellationToken);
|
.ToListAsync(cancellationToken))
|
||||||
|
.Where(x => collegeIds.Contains(x.CollegeId))
|
||||||
|
.ToList();
|
||||||
var publicCourses = courses
|
var publicCourses = courses
|
||||||
.Where(x => x.Nature is CourseNature.GeneralRequired or CourseNature.GeneralElective)
|
.Where(x => x.Nature is CourseNature.GeneralRequired or CourseNature.GeneralElective)
|
||||||
.ToList();
|
.ToList();
|
||||||
|
|||||||
@@ -13,10 +13,7 @@ public sealed class MySqlMigrationTests
|
|||||||
[Fact]
|
[Fact]
|
||||||
public void Production_migration_is_discoverable_and_generates_mysql_sql()
|
public void Production_migration_is_discoverable_and_generates_mysql_sql()
|
||||||
{
|
{
|
||||||
var options = new DbContextOptionsBuilder<AppDbContext>()
|
var options = CreateMySqlOptions();
|
||||||
.UseMySQL(
|
|
||||||
"Server=localhost;Database=jiaowu;User=__test__;Password=__not_used__;")
|
|
||||||
.Options;
|
|
||||||
using var db = new AppDbContext(options);
|
using var db = new AppDbContext(options);
|
||||||
|
|
||||||
Assert.Contains(LatestMigration, db.Database.GetMigrations());
|
Assert.Contains(LatestMigration, db.Database.GetMigrations());
|
||||||
@@ -33,4 +30,31 @@ public sealed class MySqlMigrationTests
|
|||||||
Assert.DoesNotContain("0001-01-01", script);
|
Assert.DoesNotContain("0001-01-01", script);
|
||||||
Assert.DoesNotContain("0000-00-00", 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;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user