Читать партиции из БД последовательно в 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 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);
var results = await Task.WhenAll(requestTypes.Select(async rt =>
{
await gate.WaitAsync(cancellationToken);
try
{
return await FindForTypeSafeAsync(rt, statusFilter, email, cancellationToken);
return await FindForTypeSafeAsync(rt, archivedByType[rt.Id], statusFilter, email, cancellationToken);
}
finally
{
@@ -170,11 +178,11 @@ public sealed class RequestManageService(
}
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
{
return await FindForTypeAsync(requestType, statusFilter, approverEmail, cancellationToken);
return await FindForTypeAsync(requestType, archived, statusFilter, approverEmail, cancellationToken);
}
catch (Exception ex) when (ex is not OperationCanceledException)
{
@@ -186,10 +194,9 @@ public sealed class RequestManageService(
}
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 archived = await partitionRegistry.GetArchivedAsync(requestType, cancellationToken);
var current = await partitionRegistry.BuildCurrentAsync(requestType, archived, cancellationToken);
var partitions = archived