using System.Data; using System.Text.Json; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Storage; namespace IM.InitCommon.Management; public static class ReceiptStore { public static async Task Execute(DbContext db, InternalAction command, Func> apply, CancellationToken ct) { await using var tx = await db.Database.BeginTransactionAsync(IsolationLevel.Serializable, ct); var connection = db.Database.GetDbConnection(); await using var read = connection.CreateCommand(); read.Transaction = tx.GetDbTransaction(); var owner = db.GetType().Name; read.CommandText = "SELECT Payload FROM management_receipts WHERE Owner = @owner AND Id = @id FOR UPDATE"; var ownerParameter = read.CreateParameter(); ownerParameter.ParameterName = "@owner"; ownerParameter.Value = owner; read.Parameters.Add(ownerParameter); var parameter = read.CreateParameter(); parameter.ParameterName = "@id"; parameter.Value = command.Id.ToString(); read.Parameters.Add(parameter); var existing = await read.ExecuteScalarAsync(ct); if (existing is string json) { var saved = JsonSerializer.Deserialize(json)!; if (saved.Command != command) throw new InvalidOperationException("操作 ID 已被其他请求使用"); await tx.CommitAsync(ct); return saved.Result; } var receipt = await apply(); await db.SaveChangesAsync(ct); await db.Database.ExecuteSqlInterpolatedAsync($"INSERT INTO management_receipts (Owner, Id, Payload, CreatedAt) VALUES ({owner}, {command.Id.ToString()}, {JsonSerializer.Serialize(new SavedReceipt(command, receipt))}, {DateTime.UtcNow})", ct); await tx.CommitAsync(ct); return receipt; } private record SavedReceipt(InternalAction Command, ActionReceipt Result); }