Читать партиции из БД последовательно в by-approver
Tests / frontend-test (pull_request) Successful in 47s
Tests / backend-test (pull_request) Successful in 1m33s

DbContext не потокобезопасен: параллельные задачи по типам заявок падали
с «вторая операция на контексте», а пустой catch превращал это в пустой список.
Теперь БД читается последовательно, параллельно идёт только SharePoint.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
gorbenko.pavel
2026-10-02 15:24:57 +05:00
parent 3c3c7caa27
commit feb95f5621
@@ -152,13 +152,21 @@ public sealed class RequestManageService(
var statusFilter = string.Join(" or ", activeStatuses.Select(s => $"{StatusField} eq '{Escape(s)}'")); var statusFilter = string.Join(" or ", activeStatuses.Select(s => $"{StatusField} eq '{Escape(s)}'"));
var email = approverEmail.Trim(); var email = approverEmail.Trim();
// DbContext не потокобезопасен: архивные партиции читаем из БД ПОСЛЕДОВАТЕЛЬНО,
// и только дальнейшее чтение SharePoint (без БД) идёт параллельно. Раньше
// GetArchivedAsync вызывался из параллельных задач — «вторая операция на
// контексте» падала, а пустой catch превращал это в пустой список.
var archivedByType = new Dictionary<int, IReadOnlyList<RequestListPartition>>();
foreach (var rt in requestTypes)
archivedByType[rt.Id] = await partitionRegistry.GetArchivedAsync(rt, cancellationToken);
using var gate = new SemaphoreSlim(MaxParallelTypes); using var gate = new SemaphoreSlim(MaxParallelTypes);
var results = await Task.WhenAll(requestTypes.Select(async rt => var results = await Task.WhenAll(requestTypes.Select(async rt =>
{ {
await gate.WaitAsync(cancellationToken); await gate.WaitAsync(cancellationToken);
try try
{ {
return await FindForTypeSafeAsync(rt, statusFilter, email, cancellationToken); return await FindForTypeSafeAsync(rt, archivedByType[rt.Id], statusFilter, email, cancellationToken);
} }
finally finally
{ {
@@ -170,11 +178,11 @@ public sealed class RequestManageService(
} }
private async Task<IReadOnlyList<UserApprovalRowDto>> FindForTypeSafeAsync( private async Task<IReadOnlyList<UserApprovalRowDto>> FindForTypeSafeAsync(
RequestType requestType, string statusFilter, string approverEmail, CancellationToken cancellationToken) RequestType requestType, IReadOnlyList<RequestListPartition> archived, string statusFilter, string approverEmail, CancellationToken cancellationToken)
{ {
try try
{ {
return await FindForTypeAsync(requestType, statusFilter, approverEmail, cancellationToken); return await FindForTypeAsync(requestType, archived, statusFilter, approverEmail, cancellationToken);
} }
catch (Exception ex) when (ex is not OperationCanceledException) catch (Exception ex) when (ex is not OperationCanceledException)
{ {
@@ -186,10 +194,9 @@ public sealed class RequestManageService(
} }
private async Task<IReadOnlyList<UserApprovalRowDto>> FindForTypeAsync( private async Task<IReadOnlyList<UserApprovalRowDto>> FindForTypeAsync(
RequestType requestType, string statusFilter, string approverEmail, CancellationToken cancellationToken) RequestType requestType, IReadOnlyList<RequestListPartition> archived, string statusFilter, string approverEmail, CancellationToken cancellationToken)
{ {
var siteUrl = requestType.SiteUrl!; var siteUrl = requestType.SiteUrl!;
var archived = await partitionRegistry.GetArchivedAsync(requestType, cancellationToken);
var current = await partitionRegistry.BuildCurrentAsync(requestType, archived, cancellationToken); var current = await partitionRegistry.BuildCurrentAsync(requestType, archived, cancellationToken);
var partitions = archived var partitions = archived