志愿提交、资格校验、补录限制、次数上限与自动锁定:[CandidateService.Admissions.cs (line 152)](C:/Users/BI/Documents/EIS-dotnet/src/Eis.Infrastructure/Candidate/CandidateService.Admissions.cs:152) 通用招生记录读取及事务写入:[CandidateAdmissionRepository.cs (line 25)](C:/Users/BI/Documents/EIS-dotnet/src/Eis.Infrastructure/Candidate/CandidateAdmissionRepository.cs:25) 原生路由已接入:[NativeCandidateEndpoints.cs (line 70)](C:/Users/BI/Documents/EIS-dotnet/src/Eis.Web/Candidate/NativeCandidateEndpoints.cs:70) 迁移状态现在会把 Candidate 标记为原生:[MigrationFeatureCatalog.cs (line 12)](C:/Users/BI/Documents/EIS-dotnet/src/Eis.Infrastructure/Migration/MigrationFeatureCatalog.cs:12)
133 lines
5.5 KiB
C#
133 lines
5.5 KiB
C#
using System.Data.Common;
|
|
using System.Globalization;
|
|
using System.Text.Json.Nodes;
|
|
using Eis.Infrastructure.Data;
|
|
|
|
namespace Eis.Infrastructure.Candidate;
|
|
|
|
internal sealed record CandidateAdmissionRecord(
|
|
string Id,
|
|
string Kind,
|
|
string ExamId,
|
|
string? UserId,
|
|
string? SchoolId,
|
|
string Status,
|
|
JsonObject Payload,
|
|
string CreatedAt,
|
|
string UpdatedAt);
|
|
|
|
internal sealed record CandidateAdmissionSchool(string Id, string Code, string Name);
|
|
|
|
internal sealed record CandidateAdmissionSnapshot(
|
|
IReadOnlyList<CandidateAdmissionRecord> Records,
|
|
IReadOnlyDictionary<string, CandidateAdmissionSchool> Schools);
|
|
|
|
internal sealed class CandidateAdmissionRepository(IRelationalConnectionFactory connectionFactory)
|
|
{
|
|
public async Task<CandidateAdmissionSnapshot> LoadAsync(CancellationToken cancellationToken)
|
|
{
|
|
await using var connection = await connectionFactory.OpenAsync(cancellationToken);
|
|
var records = new List<CandidateAdmissionRecord>();
|
|
await using (var command = connection.CreateCommand())
|
|
{
|
|
command.CommandText = "SELECT id, kind, exam_id, user_id, school_id, status, payload_json, created_at, updated_at FROM admission_records ORDER BY created_at, id";
|
|
await using var reader = await command.ExecuteReaderAsync(cancellationToken);
|
|
while (await reader.ReadAsync(cancellationToken))
|
|
{
|
|
records.Add(new CandidateAdmissionRecord(
|
|
ReadString(reader, "id"),
|
|
ReadString(reader, "kind"),
|
|
ReadString(reader, "exam_id"),
|
|
ReadOptionalString(reader, "user_id"),
|
|
ReadOptionalString(reader, "school_id"),
|
|
ReadString(reader, "status"),
|
|
JsonNode.Parse(ReadString(reader, "payload_json"))?.AsObject() ?? new JsonObject(),
|
|
ReadString(reader, "created_at"),
|
|
ReadString(reader, "updated_at")));
|
|
}
|
|
}
|
|
|
|
var schools = new List<CandidateAdmissionSchool>();
|
|
await using (var command = connection.CreateCommand())
|
|
{
|
|
command.CommandText = "SELECT id, code, name FROM schools ORDER BY id";
|
|
await using var reader = await command.ExecuteReaderAsync(cancellationToken);
|
|
while (await reader.ReadAsync(cancellationToken))
|
|
{
|
|
schools.Add(new CandidateAdmissionSchool(
|
|
ReadString(reader, "id"),
|
|
ReadString(reader, "code"),
|
|
ReadString(reader, "name")));
|
|
}
|
|
}
|
|
|
|
return new CandidateAdmissionSnapshot(
|
|
records,
|
|
schools.ToDictionary(item => item.Id, StringComparer.Ordinal));
|
|
}
|
|
|
|
public async Task SavePreferenceAsync(
|
|
CandidateAdmissionRecord record,
|
|
CancellationToken cancellationToken)
|
|
{
|
|
await using var connection = await connectionFactory.OpenAsync(cancellationToken);
|
|
await using var transaction = await connection.BeginTransactionAsync(cancellationToken);
|
|
try
|
|
{
|
|
await using (var delete = connection.CreateCommand())
|
|
{
|
|
delete.Transaction = transaction;
|
|
delete.CommandText = "DELETE FROM admission_records WHERE id = @id";
|
|
AddParameter(delete, "@id", record.Id);
|
|
await delete.ExecuteNonQueryAsync(cancellationToken);
|
|
}
|
|
|
|
await using (var insert = connection.CreateCommand())
|
|
{
|
|
insert.Transaction = transaction;
|
|
insert.CommandText = """
|
|
INSERT INTO admission_records (
|
|
id, kind, exam_id, user_id, school_id, status, payload_json, created_at, updated_at
|
|
) VALUES (
|
|
@id, @kind, @examId, @userId, @schoolId, @status, @payload, @createdAt, @updatedAt
|
|
)
|
|
""";
|
|
AddParameter(insert, "@id", record.Id);
|
|
AddParameter(insert, "@kind", record.Kind);
|
|
AddParameter(insert, "@examId", record.ExamId);
|
|
AddParameter(insert, "@userId", record.UserId);
|
|
AddParameter(insert, "@schoolId", record.SchoolId);
|
|
AddParameter(insert, "@status", record.Status);
|
|
AddParameter(insert, "@payload", record.Payload.ToJsonString());
|
|
AddParameter(insert, "@createdAt", record.CreatedAt);
|
|
AddParameter(insert, "@updatedAt", record.UpdatedAt);
|
|
await insert.ExecuteNonQueryAsync(cancellationToken);
|
|
}
|
|
|
|
await transaction.CommitAsync(cancellationToken);
|
|
}
|
|
catch
|
|
{
|
|
await transaction.RollbackAsync(cancellationToken);
|
|
throw;
|
|
}
|
|
}
|
|
|
|
private static void AddParameter(DbCommand command, string name, object? value)
|
|
{
|
|
var parameter = command.CreateParameter();
|
|
parameter.ParameterName = name;
|
|
parameter.Value = value ?? DBNull.Value;
|
|
command.Parameters.Add(parameter);
|
|
}
|
|
|
|
private static string ReadString(DbDataReader reader, string name) =>
|
|
Convert.ToString(reader.GetValue(reader.GetOrdinal(name)), CultureInfo.InvariantCulture) ?? string.Empty;
|
|
|
|
private static string? ReadOptionalString(DbDataReader reader, string name)
|
|
{
|
|
var ordinal = reader.GetOrdinal(name);
|
|
return reader.IsDBNull(ordinal) ? null : Convert.ToString(reader.GetValue(ordinal), CultureInfo.InvariantCulture);
|
|
}
|
|
}
|