126 lines
4.2 KiB
C#
126 lines
4.2 KiB
C#
using System.Data.Common;
|
|
using Microsoft.EntityFrameworkCore.Diagnostics;
|
|
|
|
namespace BuildFlow.Monitoring.Tests.Sync;
|
|
|
|
/// <summary>
|
|
/// 统计同步记录查询和 UPSERT 命令,并可让两个旧式先查后写查询在同一点汇合以稳定复现竞争。
|
|
/// </summary>
|
|
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;
|
|
|
|
/// <summary>访问同步记录表的 SELECT 命令数。</summary>
|
|
public int RecordSelectCount => Volatile.Read(ref _recordSelectCount);
|
|
|
|
/// <summary>包含 ON CONFLICT 的同步记录 INSERT 命令数。</summary>
|
|
public int UpsertCommandCount => Volatile.Read(ref _upsertCommandCount);
|
|
|
|
/// <summary>所有 UPSERT 是否都通过数据库参数传值。</summary>
|
|
public bool AllUpsertsParameterized => Volatile.Read(ref _parameterlessUpsertCount) == 0;
|
|
|
|
public override InterceptionResult<DbDataReader> ReaderExecuting(
|
|
DbCommand command,
|
|
CommandEventData eventData,
|
|
InterceptionResult<DbDataReader> result)
|
|
{
|
|
Count(command);
|
|
return result;
|
|
}
|
|
|
|
public override async ValueTask<InterceptionResult<DbDataReader>> ReaderExecutingAsync(
|
|
DbCommand command,
|
|
CommandEventData eventData,
|
|
InterceptionResult<DbDataReader> 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<int> NonQueryExecuting(
|
|
DbCommand command,
|
|
CommandEventData eventData,
|
|
InterceptionResult<int> result)
|
|
{
|
|
Count(command);
|
|
return result;
|
|
}
|
|
|
|
public override ValueTask<InterceptionResult<int>> NonQueryExecutingAsync(
|
|
DbCommand command,
|
|
CommandEventData eventData,
|
|
InterceptionResult<int> result,
|
|
CancellationToken cancellationToken = default)
|
|
{
|
|
Count(command);
|
|
return ValueTask.FromResult(result);
|
|
}
|
|
|
|
public override InterceptionResult<object> ScalarExecuting(
|
|
DbCommand command,
|
|
CommandEventData eventData,
|
|
InterceptionResult<object> result)
|
|
{
|
|
Count(command);
|
|
return result;
|
|
}
|
|
|
|
public override ValueTask<InterceptionResult<object>> ScalarExecutingAsync(
|
|
DbCommand command,
|
|
CommandEventData eventData,
|
|
InterceptionResult<object> 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);
|
|
}
|