From feb95f5621306fe1341ab704ded3709a8607c577 Mon Sep 17 00:00:00 2001 From: "gorbenko.pavel" Date: Fri, 2 Oct 2026 15:24:57 +0500 Subject: [PATCH] =?UTF-8?q?=D0=A7=D0=B8=D1=82=D0=B0=D1=82=D1=8C=20=D0=BF?= =?UTF-8?q?=D0=B0=D1=80=D1=82=D0=B8=D1=86=D0=B8=D0=B8=20=D0=B8=D0=B7=20?= =?UTF-8?q?=D0=91=D0=94=20=D0=BF=D0=BE=D1=81=D0=BB=D0=B5=D0=B4=D0=BE=D0=B2?= =?UTF-8?q?=D0=B0=D1=82=D0=B5=D0=BB=D1=8C=D0=BD=D0=BE=20=D0=B2=20by-approv?= =?UTF-8?q?er?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit DbContext не потокобезопасен: параллельные задачи по типам заявок падали с «вторая операция на контексте», а пустой catch превращал это в пустой список. Теперь БД читается последовательно, параллельно идёт только SharePoint. Co-Authored-By: Claude Sonnet 5.5 --- .../RequestManage/RequestManageService.cs | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/backend/Tnsu.ApprovalRoute.Application/RequestManage/RequestManageService.cs b/backend/Tnsu.ApprovalRoute.Application/RequestManage/RequestManageService.cs index 50e18aa..4cc7af0 100644 --- a/backend/Tnsu.ApprovalRoute.Application/RequestManage/RequestManageService.cs +++ b/backend/Tnsu.ApprovalRoute.Application/RequestManage/RequestManageService.cs @@ -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>(); + 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> FindForTypeSafeAsync( - RequestType requestType, string statusFilter, string approverEmail, CancellationToken cancellationToken) + RequestType requestType, IReadOnlyList 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> FindForTypeAsync( - RequestType requestType, string statusFilter, string approverEmail, CancellationToken cancellationToken) + RequestType requestType, IReadOnlyList 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