From 5f384947a6e2a09e7bcd46d96af7f974ddc71493 Mon Sep 17 00:00:00 2001 From: Mikhail Kuznetsov Date: Thu, 23 Jul 2026 08:36:17 +1000 Subject: [PATCH] =?UTF-8?q?fix(templateMatcher):=20=D0=94=D0=BE=D0=B1?= =?UTF-8?q?=D0=B0=D0=B2=D0=BB=D0=B5=D0=BD=D0=BE=20=D1=83=D0=BF=D1=80=D0=B0?= =?UTF-8?q?=D0=B2=D0=BB=D0=B5=D0=BD=D0=B8=D0=B5=20=D1=82=D1=80=D0=B0=D0=BD?= =?UTF-8?q?=D0=B7=D0=B0=D0=BA=D1=86=D0=B8=D0=B5=D0=B9=20=D0=B2=20=D0=BC?= =?UTF-8?q?=D0=BE=D0=BC=D0=B5=D0=BD=D1=82=20=D0=B7=D0=B0=D1=85=D0=B2=D0=B0?= =?UTF-8?q?=D1=82=20=D0=BD=D0=B5=D0=B8=D1=81=D0=BF=D0=BE=D0=BB=D1=8C=D0=B7?= =?UTF-8?q?=D1=83=D0=B5=D0=BC=D0=BE=D0=B3=D0=BE=20=D1=88=D0=B0=D0=B1=D0=BB?= =?UTF-8?q?=D0=BE=D0=BD=D0=B0,=20=D0=B4=D0=BB=D1=8F=20=D1=83=D1=81=D1=82?= =?UTF-8?q?=D1=80=D0=B0=D0=BD=D0=B5=D0=BD=D0=B8=D1=8F=20=D0=B3=D0=BE=D0=BD?= =?UTF-8?q?=D0=BA=D0=B8.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- PARR.DAL/Repositories/TemplateRepository.cs | 116 ++++++++++---------- 1 file changed, 59 insertions(+), 57 deletions(-) diff --git a/PARR.DAL/Repositories/TemplateRepository.cs b/PARR.DAL/Repositories/TemplateRepository.cs index 4a81b015..e962384e 100644 --- a/PARR.DAL/Repositories/TemplateRepository.cs +++ b/PARR.DAL/Repositories/TemplateRepository.cs @@ -99,66 +99,69 @@ namespace PARR.DAL.Repositories public async Task ReserveUnusedTemplateAsync(Guid newUnitId, HistoryInitiator initiator) { - logger.LogDebug("Резервирую неиспользуемый шаблон с проверкой конфигураций роботов для UnitId: {UnitId}", newUnitId); + logger.LogDebug("Резервирую неиспользуемый шаблон для UnitId: {UnitId}", newUnitId); - var sql = @" - UPDATE ""Templates"" - SET ""StatusTypeId"" = @NewStatus, - ""DateModified"" = @DateModified, - ""InitiatorIp"" = @InitiatorIp, - ""InitiatorParrComponentId"" = @InitiatorComponent, - ""InitiatorComment"" = @InitiatorComment - WHERE ""Id"" = ( - SELECT t.""Id"" - FROM ""Templates"" t - WHERE t.""StatusTypeId"" = @OldStatus - AND t.""UnitId"" != @NewUnitId - AND EXISTS ( - SELECT 1 - FROM ""RobotConfigurations"" rc - WHERE rc.""TemplateId"" = t.""Id"" - AND rc.""RobotCode"" = @RobotCode1 - AND rc.""TaskStatusCode"" = @TaskStatus - AND rc.""RobotStatusCode"" = @RobotStatus - ) - AND EXISTS ( - SELECT 1 - FROM ""RobotConfigurations"" rc - WHERE rc.""TemplateId"" = t.""Id"" - AND rc.""RobotCode"" = @RobotCode2 - AND rc.""TaskStatusCode"" = @TaskStatus - AND rc.""RobotStatusCode"" = @RobotStatus - ) - ORDER BY t.""DateCreated"" ASC - LIMIT 1 - ) - RETURNING ""Id"";"; - - var parameters = new[] - { - new NpgsqlParameter("@NewStatus", (int)TemplateStatusTypeEnum.Updating), - new NpgsqlParameter("@DateModified", DateTimeOffset.UtcNow), - new NpgsqlParameter("@InitiatorIp", initiator.InitiatorIp ?? (object)DBNull.Value), - new NpgsqlParameter("@InitiatorComponent", - initiator.InitiatorParrComponentId.HasValue - ? (object)(int)initiator.InitiatorParrComponentId.Value - : DBNull.Value), - new NpgsqlParameter("@InitiatorComment", initiator.InitiatorComment ?? (object)DBNull.Value), - new NpgsqlParameter("@OldStatus", (int)TemplateStatusTypeEnum.Unused), - new NpgsqlParameter("@NewUnitId", newUnitId), - // Параметры для проверки конфигураций роботов - new NpgsqlParameter("@RobotCode1", (int)RobotsEnum.TemplateOrder), - new NpgsqlParameter("@RobotCode2", (int)RobotsEnum.ScheduleOrder), - new NpgsqlParameter("@TaskStatus", (int)TaskStatusEnum.Ok), - new NpgsqlParameter("@RobotStatus", (int)RobotStatusEnum.Complete) - }; + // Явная транзакция гарантирует атомарность UPDATE + подзапроса + await using var transaction = await EntityContext.Database.BeginTransactionAsync(); try { + var sql = @" + UPDATE ""Templates"" + SET ""StatusTypeId"" = @NewStatus, + ""DateModified"" = @DateModified, + ""InitiatorIp"" = @InitiatorIp, + ""InitiatorParrComponentId"" = @InitiatorComponent, + ""InitiatorComment"" = @InitiatorComment + WHERE ""Id"" = ( + SELECT t.""Id"" + FROM ""Templates"" t + WHERE t.""StatusTypeId"" = @OldStatus + AND t.""UnitId"" != @NewUnitId + AND EXISTS ( + SELECT 1 FROM ""RobotConfigurations"" rc + WHERE rc.""TemplateId"" = t.""Id"" + AND rc.""RobotCode"" = @RobotCode1 + AND rc.""TaskStatusCode"" = @TaskStatus + AND rc.""RobotStatusCode"" = @RobotStatus + ) + AND EXISTS ( + SELECT 1 FROM ""RobotConfigurations"" rc + WHERE rc.""TemplateId"" = t.""Id"" + AND rc.""RobotCode"" = @RobotCode2 + AND rc.""TaskStatusCode"" = @TaskStatus + AND rc.""RobotStatusCode"" = @RobotStatus + ) + ORDER BY t.""DateCreated"" ASC + LIMIT 1 + FOR UPDATE SKIP LOCKED + ) + RETURNING ""Id"";"; + + var parameters = new[] + { + new NpgsqlParameter("@NewStatus", (int)TemplateStatusTypeEnum.Updating), + new NpgsqlParameter("@DateModified", DateTimeOffset.UtcNow), + new NpgsqlParameter("@InitiatorIp", initiator.InitiatorIp ?? (object)DBNull.Value), + new NpgsqlParameter("@InitiatorComponent", + initiator.InitiatorParrComponentId.HasValue + ? (object)(int)initiator.InitiatorParrComponentId.Value + : DBNull.Value), + new NpgsqlParameter("@InitiatorComment", initiator.InitiatorComment ?? (object)DBNull.Value), + new NpgsqlParameter("@OldStatus", (int)TemplateStatusTypeEnum.Unused), + new NpgsqlParameter("@NewUnitId", newUnitId), + new NpgsqlParameter("@RobotCode1", (int)RobotsEnum.TemplateOrder), + new NpgsqlParameter("@RobotCode2", (int)RobotsEnum.ScheduleOrder), + new NpgsqlParameter("@TaskStatus", (int)TaskStatusEnum.Ok), + new NpgsqlParameter("@RobotStatus", (int)RobotStatusEnum.Complete) + }; + var result = await EntityContext.Database .SqlQueryRaw(sql, parameters) .ToListAsync(); + await transaction.CommitAsync(); + var reservedTemplateId = result.FirstOrDefault(); if (reservedTemplateId != Guid.Empty) @@ -167,14 +170,13 @@ namespace PARR.DAL.Repositories reservedTemplateId, newUnitId); return reservedTemplateId; } - else - { - logger.LogDebug("Не удалось зарезервировать шаблон для UnitId: {UnitId} (не найдено подходящих конфигураций роботов)", newUnitId); - return null; - } + + logger.LogDebug("Не удалось зарезервировать шаблон для UnitId: {UnitId}", newUnitId); + return null; } catch (Exception ex) { + await transaction.RollbackAsync(); logger.LogError(ex, "Ошибка при резервировании шаблона для UnitId: {UnitId}", newUnitId); throw; }