using System.Data.Common; using Microsoft.EntityFrameworkCore.Diagnostics; namespace BuildFlow.Monitoring.Tests.Sync; /// /// 统计同步记录查询和 UPSERT 命令,并可让两个旧式先查后写查询在同一点汇合以稳定复现竞争。 /// public sealed class CountingUpsertInterceptor(int concurrentSelectParticipants = 0) : DbCommandInterceptor { private readonly TaskCompletionSource _selectBarrier = new(TaskCreationOptions.RunContinuationsAsynchronously); private int _recordSelectCount; private int _upsertCommandCount; private int _parameterlessUpsertCount; private int _selectBarrierArrivals; /// 访问同步记录表的 SELECT 命令数。 public int RecordSelectCount => Volatile.Read(ref _recordSelectCount); /// 包含 ON CONFLICT 的同步记录 INSERT 命令数。 public int UpsertCommandCount => Volatile.Read(ref _upsertCommandCount); /// 所有 UPSERT 是否都通过数据库参数传值。 public bool AllUpsertsParameterized => Volatile.Read(ref _parameterlessUpsertCount) == 0; public override InterceptionResult ReaderExecuting( DbCommand command, CommandEventData eventData, InterceptionResult result) { Count(command); return result; } public override async ValueTask> ReaderExecutingAsync( DbCommand command, CommandEventData eventData, InterceptionResult result, CancellationToken cancellationToken = default) { Count(command); if (concurrentSelectParticipants > 0 && IsRecordSelect(command)) { var arrivals = Interlocked.Increment(ref _selectBarrierArrivals); if (arrivals == concurrentSelectParticipants) { _selectBarrier.TrySetResult(); } if (arrivals <= concurrentSelectParticipants) { await _selectBarrier.Task.WaitAsync(TimeSpan.FromSeconds(10), cancellationToken); } } return result; } public override InterceptionResult NonQueryExecuting( DbCommand command, CommandEventData eventData, InterceptionResult result) { Count(command); return result; } public override ValueTask> NonQueryExecutingAsync( DbCommand command, CommandEventData eventData, InterceptionResult result, CancellationToken cancellationToken = default) { Count(command); return ValueTask.FromResult(result); } public override InterceptionResult ScalarExecuting( DbCommand command, CommandEventData eventData, InterceptionResult result) { Count(command); return result; } public override ValueTask> ScalarExecutingAsync( DbCommand command, CommandEventData eventData, InterceptionResult result, CancellationToken cancellationToken = default) { Count(command); return ValueTask.FromResult(result); } private void Count(DbCommand command) { if (IsRecordSelect(command)) { Interlocked.Increment(ref _recordSelectCount); } if (!IsUpsert(command)) { return; } Interlocked.Increment(ref _upsertCommandCount); if (command.Parameters.Count == 0) { Interlocked.Increment(ref _parameterlessUpsertCount); } } private static bool IsRecordSelect(DbCommand command) => command.CommandText.TrimStart().StartsWith("SELECT", StringComparison.OrdinalIgnoreCase) && command.CommandText.Contains("b_synced_records", StringComparison.OrdinalIgnoreCase); private static bool IsUpsert(DbCommand command) => command.CommandText.TrimStart().StartsWith("INSERT", StringComparison.OrdinalIgnoreCase) && command.CommandText.Contains("b_synced_records", StringComparison.OrdinalIgnoreCase) && command.CommandText.Contains("ON CONFLICT", StringComparison.OrdinalIgnoreCase); }