diff --git a/PARR.Core/Services/RobotTask/Implementations/RobotTaskService.cs b/PARR.Core/Services/RobotTask/Implementations/RobotTaskService.cs index 3757b276..e44c506a 100644 --- a/PARR.Core/Services/RobotTask/Implementations/RobotTaskService.cs +++ b/PARR.Core/Services/RobotTask/Implementations/RobotTaskService.cs @@ -48,13 +48,13 @@ namespace PARR.Core.Services.RobotTask.Implementations ITemplateRenamePendingRepository templateRenamePendingRepository ) { - this._logger = logger; - this._robotConfigurationRepository = robotConfigurationRepository; - this._settingsFromDb = settingsFromDb; - this._robotHistoryRepository = robotHistoryRepository; - this._mapper = mapper; - this._shortcodesService = shortcodesService; - this._nextRunService = nextRunService; + _logger = logger; + _robotConfigurationRepository = robotConfigurationRepository; + _settingsFromDb = settingsFromDb; + _robotHistoryRepository = robotHistoryRepository; + _mapper = mapper; + _shortcodesService = shortcodesService; + _nextRunService = nextRunService; _templateRenamePendingRepository = templateRenamePendingRepository; } @@ -264,17 +264,18 @@ namespace PARR.Core.Services.RobotTask.Implementations // Ищем есть ли связанные шаблоны с таким имененм на переименование - var taskTemplateNames = tasks.Select(t => t.TemplateName); + var taskTemplateNames = tasks.Select(t => t.TemplateName).Distinct().ToList(); + // Ищем записи в таблице переименований, где OldName совпадает с именами наших новых задач var templatesToRename = await _templateRenamePendingRepository.Get().AsNoTracking() .Where(t => taskTemplateNames.Contains(t.OldName)) .ToListAsync(); - _logger.LogDebug("Шаблонов для переименования в текущем задании: {Count} шт.", templatesToRename.Count); + _logger.LogDebug("Найдено шаблонов в процессе переименования для текущих задач: {Count} шт.", templatesToRename.Count); if (templatesToRename.Count == 0) return tasks; - // Ищем связанные переименованные шаблоны по ИД, смотрим можем ли взять их в работу + // Ищем конфигурации роботов для СТАРЫХ шаблонов (которые переименовываются) по ИД, смотрим, можем ли взять их в работу var renameTemplateIds = templatesToRename.Select(t => t.TemplateId).ToList(); var renameTasks = await _robotConfigurationRepository.Get() .AsNoTracking() @@ -286,63 +287,93 @@ namespace PARR.Core.Services.RobotTask.Implementations && t.TaskStatusCode == (int)TaskStatusEnum.Updating ).ToListAsync(); - // Ищем связанные задания которые в ошибке, и ставим целевому шаблону задание в ошибку - var errorTasks = renameTasks.Where(t => t.RobotStatusCode == (int)RobotStatusEnum.Error && t.AttemptsNumber >= _settingsFromDb.RobotAttemptsNumber).ToList(); + // --- Блок обработки ошибок --- + + // Если старый шаблон в ошибке и лимит попыток исчерпан, ставим ошибку и новому шаблону + var errorTasks = renameTasks + .Where(t => + t.RobotStatusCode == (int)RobotStatusEnum.Error + && t.AttemptsNumber >= _settingsFromDb.RobotAttemptsNumber + ).ToList(); var tasksToSetErrorStatus = new List(); if (errorTasks.Count > 0) { - _logger.LogDebug("Найдено связанных заданий на переименование с ошибками: {ErrorCount}. Будем целевым шаблонам тоже ставить статус ошибки.", errorTasks.Count); + _logger.LogDebug("Найдено старых заданий на переименование с ошибками: {ErrorCount}. Ставим ошибку целевым (новым) заданиям.", errorTasks.Count); - var errorTemplateNames = errorTasks.Select(t => t.Template!.Name).ToList(); + var errorTemplateNames = errorTasks.Select(t => t.Template!.Name).ToHashSet(); // Берем целевые таски, находим в них задания которым надо поставить ошибку - tasksToSetErrorStatus = tasks.Where(t => errorTemplateNames.Contains(t.TemplateName)).Select(t => t.TaskId).ToList(); + tasksToSetErrorStatus = tasks + .Where(t => errorTemplateNames.Contains(t.TemplateName)) + .Select(t => t.TaskId) + .ToList(); - // Устанавливаем ошибку целевым + пишем комментарий от робота + нажимаем комит - var logMessage = "[RobotTaskService] Установлен статус ошибки, так как не переименован связанный шаблон"; - await SetErrorStatusAsync(tasksToSetErrorStatus, logMessage); + if (tasksToSetErrorStatus.Count > 0) + { + // Устанавливаем ошибку целевым + пишем комментарий от робота + нажимаем комит + var logMessage = "[RobotTaskService] Установлен статус ошибки, так как не переименован связанный шаблон"; + await SetErrorStatusAsync(tasksToSetErrorStatus, logMessage); + } } + // --- Блок подмены задач --- + var endDate = DateTimeOffset.UtcNow.Add(-_settingsFromDb.RobotWaitTime); - // Смотрим статусы роботов, можно взять в работу, только если (RobotStatus == Wait) или (InpRogress но которые еще не просрочены) - var allowedTasks = renameTasks.Where(t => + + // Фильтруем старые задачи, которые МОЖНО взять в работу. Смотрим статусы роботов, можно взять в работу, только если (RobotStatus == Wait) или (InpRogress но которые еще не просрочены) + var allowedRenameTasks = renameTasks.Where(t => t.RobotStatusCode == (int)RobotStatusEnum.Wait - || (t.RobotStatusCode == (int)RobotStatusEnum.InProgress && t.AttemptsNumber < _settingsFromDb.RobotAttemptsNumber && t.LastRobotStatusUpdated < endDate) - ); + || (t.RobotStatusCode == (int)RobotStatusEnum.InProgress + && t.AttemptsNumber < _settingsFromDb.RobotAttemptsNumber + && t.LastRobotStatusUpdated < endDate) + ).ToList(); - // Формируем список заданий + // Словарь для поиска подменной задачи по имени шаблона. + // GroupBy + First на случай, если в бд есть дубликаты, но такого быть не может + var renameTasksToDictionary = allowedRenameTasks + .GroupBy(t => t.Template!.Name) + .ToDictionary( + t => t.Key, + t => new RobotTaskDetails(t.First().Id, t.First().Template!.Name, t.First().Template!.NextRun) + ); - var originalCount = tasks.Count; // Запоминаем сколько было изначально + var errorTaskIdsSet = tasksToSetErrorStatus.ToHashSet(); - // Из исходных тасков удалить те которым установлен статус ошибки - var filteredOriginalTasks = tasks.Where(t => !tasksToSetErrorStatus.Contains(t.TaskId)).ToList(); - var errorCount = tasksToSetErrorStatus.Count; // Столько ушло в ошибку + // Создаем итоговый список + var finalTasks = new List(tasks.Count); + int replacedCount = 0; + int errorCount = errorTaskIdsSet.Count; - // Определяем, какие имена шаблонов мы БУДЕМ подменять на задачи переименования - var allowedRenameTemplateNames = allowedTasks.Select(t => t.Template!.Name).ToList(); + // Проходим по ИСХОДНОМУ списку, чтобы сохранить его порядок сортировки + foreach (var task in tasks) + { + // Если задаче нужно поставить ошибку, просто пропускаем ее (она не попадет в итоговый список) + if (errorTaskIdsSet.Contains(task.TaskId)) + { + continue; + } - // Из исходных удаляем те, которые мы сейчас заменим (подменим) - var resultsTasks = filteredOriginalTasks.Where(t => !allowedRenameTemplateNames.Contains(t.TemplateName)).ToList(); + // Если для этого имени шаблона есть разрешенная задача на переименование - вставляем ее на место текущей + if (renameTasksToDictionary.TryGetValue(task.TemplateName, out var renameTask)) + { + finalTasks.Add(renameTask); + replacedCount++; + } + else + { + // Иначе оставляем исходную задачу на месте + finalTasks.Add(task); + } + } - // Считаем сколько именно задач мы ВЫКИНУЛИ из исходного списка ради подмены - var replacedCount = filteredOriginalTasks.Count - resultsTasks.Count; - - // В исходные добавляем подменные таски для переименования (мапим их в RobotTaskDetails) - var mappedRenameTasks = allowedTasks.Select(t => new RobotTaskDetails(t.Id, t.Template!.Name, t.Template.NextRun)).ToList(); - resultsTasks.AddRange(mappedRenameTasks); - - var injectedCount = mappedRenameTasks.Count; // Столько задач переименования добавили взамен - - // Логируем итоговую статистику трансформации пула задач - // TODO: Позже этот лог можно понизить до Debug!!! _logger.LogInformation( - "Трансформация пула задач завершена. Исходных: {OriginalCount} шт. " + - "Отклонено (ошибка): {ErrorCount} шт. Удалено обычных для подмены: {ReplacedCount} шт. " + - "Внедрено задач переименования: {InjectedCount} шт. Итого к выдаче: {FinalCount} шт.", - originalCount, errorCount, replacedCount, injectedCount, resultsTasks.Count); + "Трансформация пула задач (Rename). Исходных: {OriginalCount}. Отклонено (Error): {ErrorCount}. " + + "Заменено на старые: {ReplacedCount}. Итого к выдаче: {FinalCount}", + tasks.Count, errorCount, replacedCount, finalTasks.Count + ); - // Затем отсортируем по NextRun, чтоб ближайшие были выше - return resultsTasks.OrderBy(t => t.NextRun).ToList(); + // Возвращаем без дополнительной сортировки по NextRun. Порядок сохранен начального списка + return finalTasks; } @@ -353,9 +384,13 @@ namespace PARR.Core.Services.RobotTask.Implementations /// private async Task SetErrorStatusAsync(List taskIds, string logMessage) { + if (taskIds == null || taskIds.Count == 0) + return; + var tasks = await _robotConfigurationRepository.Get() .Include(t => t.Template) - .Where(t => taskIds.Contains(t.Id)).ToListAsync(); + .Where(t => taskIds.Contains(t.Id)) + .ToListAsync(); if (tasks.Count == 0) return; @@ -380,16 +415,19 @@ namespace PARR.Core.Services.RobotTask.Implementations await _robotHistoryRepository.CreateAsync(history); - _logger.LogInformation("Для целевого задания {TaskId}, по переименованию шаблона в {TemplateName}, установлен статус ошибки, так как связанное задание со старым шаблоном со статусом ошибка.", task.Id, task.Template!.Name); + _logger.LogInformation("Для целевого задания {TaskId} (шаблон '{TemplateName}') установлен статус ошибки, " + + "так как связанное задание со старым шаблоном не было успешно выполнено.", + task.Id, task.Template!.Name); } if (await _robotHistoryRepository.CommitAsync()) { - _logger.LogDebug("Установлен статус Ошибка, для заданий {TaskCount} шт.", tasks.Count); + _logger.LogDebug("Установлен статус 'Ошибка', для заданий {TaskCount} шт.", tasks.Count); } else { - _logger.LogError("Ошибка при установке статуса задания Ошибка, для заданий {TaskCount} шт.", tasks.Count); + _logger.LogError("Ошибка при установке статуса задания 'Ошибка', для заданий {TaskCount} шт. Транзакция отменена", tasks.Count); + throw new DbErrorException("Не удалось сохранить изменения статусов заданий при обработке переименования шаблона."); } } @@ -531,19 +569,19 @@ namespace PARR.Core.Services.RobotTask.Implementations if (!nextRun.HasValue) { - _logger.LogError("При обновлении nextRun для шаблона {templateId}, расчитанный nextRun=null, ошибка в расчетах.", template.Id); + _logger.LogError("При обновлении nextRun для шаблона {TemplateId}, расчитанный nextRun=null, ошибка в расчетах.", template.Id); return false; } if (nextRun.Value < DateTimeOffset.UtcNow) { - _logger.LogError("При обновлении nextRun для шаблона {templateId}, расчитанный nextRun : IBaseRepository where T : class, IBaseEntity { - protected readonly ILogger logger; + protected readonly ILogger _logger; protected readonly DbSet EntitySet; protected readonly DataContext EntityContext; protected BaseRepository(ILogger logger, DataContext dataContext) { - this.logger = logger; + this._logger = logger; this.EntityContext = dataContext; this.EntitySet = dataContext.Set(); } @@ -29,7 +29,7 @@ namespace PARR.DAL.Repositories.Base public virtual async Task AddRangeAsync(List objs) { - logger.LogDebug("Начинаю добавление диапазона объектов типа {EntityType}, количество: {Count}", + _logger.LogDebug("Начинаю добавление диапазона объектов типа {EntityType}, количество: {Count}", typeof(T).Name, objs.Count); objs.ForEach(item => item.DateCreated = DateTimeOffset.UtcNow); @@ -37,26 +37,26 @@ namespace PARR.DAL.Repositories.Base try { await EntitySet.AddRangeAsync(objs); - logger.LogDebug("Успешно добавлено {Count} объектов типа {EntityType}", + _logger.LogDebug("Успешно добавлено {Count} объектов типа {EntityType}", objs.Count, typeof(T).Name); return true; } catch (Exception ex) { - logger.LogError(ex, "Ошибка при добавлении диапазона объектов типа {EntityType}", typeof(T).Name); + _logger.LogError(ex, "Ошибка при добавлении диапазона объектов типа {EntityType}", typeof(T).Name); return false; } } public async Task CommitAsync(IHistoryInitiator? initiator = null) { - logger.LogDebug("Начинаю сохранение изменений в БД для объектов типа {EntityType}", typeof(T).Name); + _logger.LogDebug("Начинаю сохранение изменений в БД для объектов типа {EntityType}", typeof(T).Name); #region Изменения var modifiedEntrities = EntityContext.ChangeTracker.Entries() .Where(t => t.State == EntityState.Modified/* || t.State == EntityState.Deleted*/); - logger.LogDebug("Найдено {Count} измененных сущностей для обработки истории", modifiedEntrities.Count()); + _logger.LogDebug("Найдено {Count} измененных сущностей для обработки истории", modifiedEntrities.Count()); foreach (var obj in modifiedEntrities) { @@ -73,13 +73,13 @@ namespace PARR.DAL.Repositories.Base try { var changedCount = await EntityContext.SaveChangesAsync(); - logger.LogDebug("Успешно сохранено {ChangedCount} изменений в БД для объектов типа {EntityType}", + _logger.LogDebug("Успешно сохранено {ChangedCount} изменений в БД для объектов типа {EntityType}", changedCount, typeof(T).Name); return true; } catch (Exception ex) { - logger.LogError(ex, "Ошибка при сохранении изменений в БД для объектов типа {EntityType}", typeof(T).Name); + _logger.LogError(ex, "Ошибка при сохранении изменений в БД для объектов типа {EntityType}", typeof(T).Name); return false; } } @@ -94,14 +94,14 @@ namespace PARR.DAL.Repositories.Base if (initiator == null) return; - logger.LogDebug("Устанавливаю инициатора для изменений"); + _logger.LogDebug("Устанавливаю инициатора для изменений"); // Задаем инициатора только для новых и измененных записей var entrities = EntityContext.ChangeTracker.Entries() .Where(t => t.State == EntityState.Modified || t.State == EntityState.Added); var entityCount = entrities.Count(); - logger.LogDebug("Найдено {Count} сущностей для установки инициатора", entityCount); + _logger.LogDebug("Найдено {Count} сущностей для установки инициатора", entityCount); // смотрим есть ли у объекта интерфейс IHistoryInitiator, если есть, задаём значения foreach (var obj in entrities) @@ -113,7 +113,7 @@ namespace PARR.DAL.Repositories.Base (obj.Entity as IHistoryInitiator)!.InitiatorParrComponentId = initiator?.InitiatorParrComponentId ?? null; (obj.Entity as IHistoryInitiator)!.InitiatorComment = initiator?.InitiatorComment ?? null; - logger.LogDebug("Установлен инициатор для сущности типа {EntityType}", obj.Entity.GetType().Name); + _logger.LogDebug("Установлен инициатор для сущности типа {EntityType}", obj.Entity.GetType().Name); } } } @@ -140,12 +140,12 @@ namespace PARR.DAL.Repositories.Base if (!isManual) { - logger.LogDebug("Обновляю DateModified для сущности типа {EntityType}", obj.Entity.GetType().Name); + _logger.LogDebug("Обновляю DateModified для сущности типа {EntityType}", obj.Entity.GetType().Name); entity.DateModified = DateTimeOffset.UtcNow; } else { - logger.LogDebug("Пропуск обновления DateModified (ManualControl) для {EntityType}", entityType.Name); + _logger.LogDebug("Пропуск обновления DateModified (ManualControl) для {EntityType}", entityType.Name); } } } @@ -156,7 +156,7 @@ namespace PARR.DAL.Repositories.Base /// private void TableHistoryResolver(EntityEntry obj) { - logger.LogDebug("Проверяю необходимость создания истории для сущности типа {EntityType}", obj.Entity.GetType().Name); + _logger.LogDebug("Проверяю необходимость создания истории для сущности типа {EntityType}", obj.Entity.GetType().Name); var myHistoryInterface = obj.Entity.GetType().GetInterfaces() .Where(t => t.IsGenericType) @@ -166,7 +166,7 @@ namespace PARR.DAL.Repositories.Base // у этого объекта нет интерфейса IMyHistory<>. Не ведем историю if (myHistoryInterface == null) { - logger.LogDebug("Сущность типа {EntityType} не требует ведения истории", obj.Entity.GetType().Name); + _logger.LogDebug("Сущность типа {EntityType} не требует ведения истории", obj.Entity.GetType().Name); return; } @@ -176,13 +176,13 @@ namespace PARR.DAL.Repositories.Base var historyType = myHistoryInterface.GetGenericArguments().First(); var historyProps = historyType.GetProperties(/*BindingFlags.DeclaredOnly | */ /*BindingFlags.Public*/).ToList(); - logger.LogDebug("Создаю историю для сущности типа {EntityType}, тип истории: {HistoryType}", + _logger.LogDebug("Создаю историю для сущности типа {EntityType}, тип истории: {HistoryType}", obj.Entity.GetType().Name, historyType.Name); var historyInstance = Activator.CreateInstance(historyType); if (historyInstance == null) { - logger.LogError("Не смог создать инстанс для ведения истории {HistoryType}", historyType.Name); + _logger.LogError("Не смог создать инстанс для ведения истории {HistoryType}", historyType.Name); return; } @@ -198,11 +198,11 @@ namespace PARR.DAL.Repositories.Base try { EntityContext.Add(historyInstance); - logger.LogDebug("История добавлена для сущности типа {EntityType}", obj.Entity.GetType().Name); + _logger.LogDebug("История добавлена для сущности типа {EntityType}", obj.Entity.GetType().Name); } catch (Exception ex) { - logger.LogError(ex, "Ошибка при добавлении объекта в историю {HistoryType}", historyType.Name); + _logger.LogError(ex, "Ошибка при добавлении объекта в историю {HistoryType}", historyType.Name); } } @@ -215,7 +215,7 @@ namespace PARR.DAL.Repositories.Base /// private void FillHistoryProps(EntityEntry originalObj, ref object historyInstance, List propsList) { - logger.LogDebug("Заполняю историю для сущности типа {EntityType}", originalObj.Entity.GetType().Name); + _logger.LogDebug("Заполняю историю для сущности типа {EntityType}", originalObj.Entity.GetType().Name); foreach (var prop in propsList) { @@ -240,7 +240,7 @@ namespace PARR.DAL.Repositories.Base histProp.SetValue(historyInstance, origValues); } - logger.LogDebug("Завершено заполнение истории для сущности типа {EntityType}", originalObj.Entity.GetType().Name); + _logger.LogDebug("Завершено заполнение истории для сущности типа {EntityType}", originalObj.Entity.GetType().Name); } @@ -256,14 +256,14 @@ namespace PARR.DAL.Repositories.Base var histProp = instanceObj.GetType().GetProperty(propName); if (histProp == null) { - logger.LogError("При изменении объекта для БД, не найдено свойство {PropertyName}", propName); + _logger.LogError("При изменении объекта для БД, не найдено свойство {PropertyName}", propName); return; } // сравним типы if (histProp.PropertyType != typeof(TValue)) { - logger.LogError("При изменении объекта для БД, не совпадают типы у свойства {PropertyName}, {PropertyType}!={ValueType}", + _logger.LogError("При изменении объекта для БД, не совпадают типы у свойства {PropertyName}, {PropertyType}!={ValueType}", propName, histProp.PropertyType.Name, typeof(TValue).Name); return; } @@ -330,7 +330,7 @@ namespace PARR.DAL.Repositories.Base public virtual async Task CreateAsync(T obj) { - logger.LogDebug("Начинаю создание объекта типа {EntityType}", typeof(T).Name); + _logger.LogDebug("Начинаю создание объекта типа {EntityType}", typeof(T).Name); if (obj.DateCreated == DateTimeOffset.MinValue) obj.DateCreated = DateTimeOffset.UtcNow; @@ -338,73 +338,73 @@ namespace PARR.DAL.Repositories.Base try { await EntitySet.AddAsync(obj); - logger.LogDebug("Объект типа {EntityType} добавлен в контекст", typeof(T).Name); + _logger.LogDebug("Объект типа {EntityType} добавлен в контекст", typeof(T).Name); return true; } catch (Exception ex) { - logger.LogError(ex, "Ошибка при добавлении объекта типа {EntityType} в БД", typeof(T).Name); + _logger.LogError(ex, "Ошибка при добавлении объекта типа {EntityType} в БД", typeof(T).Name); return false; } } public virtual bool Delete(T obj) { - logger.LogDebug("Начинаю удаление объекта типа {EntityType}", obj.GetType().Name); + _logger.LogDebug("Начинаю удаление объекта типа {EntityType}", obj.GetType().Name); try { EntitySet.Remove(obj); - logger.LogDebug("Объект типа {EntityType} удален из контекста", obj.GetType().Name); + _logger.LogDebug("Объект типа {EntityType} удален из контекста", obj.GetType().Name); return true; } catch (Exception ex) { - logger.LogError(ex, "Ошибка при удалении объекта типа {EntityType} из БД", obj.GetType().Name); + _logger.LogError(ex, "Ошибка при удалении объекта типа {EntityType} из БД", obj.GetType().Name); return false; } } public virtual async Task DeleteAsync(Guid id) { - logger.LogDebug("Начинаю удаление объекта типа {EntityType} по ID: {Id}", typeof(T).Name, id); + _logger.LogDebug("Начинаю удаление объекта типа {EntityType} по ID: {Id}", typeof(T).Name, id); try { var exist = await GetAsync(id); if (exist == null) { - logger.LogError("Ошибка при удалении из БД. Не найдена запись в БД типа {EntityType} с id: {Id}", + _logger.LogError("Ошибка при удалении из БД. Не найдена запись в БД типа {EntityType} с id: {Id}", typeof(T).Name, id); return false; } EntitySet.Remove(exist); - logger.LogDebug("Объект типа {EntityType} с ID {Id} удален из контекста", typeof(T).Name, id); + _logger.LogDebug("Объект типа {EntityType} с ID {Id} удален из контекста", typeof(T).Name, id); return true; } catch (Exception ex) { - logger.LogError(ex, "Ошибка при удалении объекта типа {EntityType} из БД по ID: {Id}", typeof(T).Name, id); + _logger.LogError(ex, "Ошибка при удалении объекта типа {EntityType} из БД по ID: {Id}", typeof(T).Name, id); return false; } } public virtual IQueryable Get() { - logger.LogDebug("Получаю набор объектов типа {EntityType}", typeof(T).Name); + _logger.LogDebug("Получаю набор объектов типа {EntityType}", typeof(T).Name); return EntitySet; } public virtual async Task GetAsync(Guid id) { - logger.LogDebug("Получаю объект типа {EntityType} по ID: {Id}", typeof(T).Name, id); + _logger.LogDebug("Получаю объект типа {EntityType} по ID: {Id}", typeof(T).Name, id); return await EntitySet.FirstOrDefaultAsync(t => t.Id == id); } public virtual IQueryable GetPage(IQueryable query, PaginationFilter paginationFilter) { - logger.LogDebug("Получаю страницу объектов типа {EntityType}, страница: {PageNumber}, размер: {PageSize}", + _logger.LogDebug("Получаю страницу объектов типа {EntityType}, страница: {PageNumber}, размер: {PageSize}", typeof(T).Name, paginationFilter.PageNumber, paginationFilter.PageSize); int skip = (paginationFilter.PageNumber - 1) * paginationFilter.PageSize; diff --git a/PARR.DAL/Repositories/RobotConfigurationRepository.cs b/PARR.DAL/Repositories/RobotConfigurationRepository.cs index ba4909ed..b8db45b1 100644 --- a/PARR.DAL/Repositories/RobotConfigurationRepository.cs +++ b/PARR.DAL/Repositories/RobotConfigurationRepository.cs @@ -54,7 +54,7 @@ namespace PARR.DAL.Repositories ? ((TaskStatusEnum)taskStatusValue).ToString() : $"Unknown ({taskStatusValue})"; - logger.LogInformation("Нельзя установить статус {newStatus} для конфигурации {configurationId}, templateId: {templateId}, так как текущий статус {currentStatus}", + _logger.LogInformation("Нельзя установить статус {newStatus} для конфигурации {configurationId}, templateId: {templateId}, так как текущий статус {currentStatus}", updatingStatus, configuration.Id, configuration.TemplateId, taskStatusName); return false; } @@ -65,13 +65,13 @@ namespace PARR.DAL.Repositories // есть ли связь у config с templetes, может инклуда нет, мало ли if (configuration.Template == null) { - logger.LogWarning("При изменении статуса задания на обновление шаблона, не смог проверить наличае ScheduleEsppId, так как нет Include с Templates. Пропустил эту проверку. configurationId: {configurationId}", configuration.Id); + _logger.LogWarning("При изменении статуса задания на обновление шаблона, не смог проверить наличае ScheduleEsppId, так как нет Include с Templates. Пропустил эту проверку. configurationId: {configurationId}", configuration.Id); } else { if (configuration.Template.ScheduleEsppId == null) { - logger.LogInformation("Нельзя установить статус {newStatus} для конфигурации {configurationId}, templateId: {templateId}, так как у шаблона отсутсвтует ScheduleEsppId=null", + _logger.LogInformation("Нельзя установить статус {newStatus} для конфигурации {configurationId}, templateId: {templateId}, так как у шаблона отсутсвтует ScheduleEsppId=null", updatingStatus, configuration.Id, configuration.TemplateId); return false; } @@ -80,7 +80,7 @@ namespace PARR.DAL.Repositories // Статус ОК, можно ставить Updating ChangeTaskStatus(updatingStatus, configuration); - logger.LogInformation("Установлен статус {newStatus} для конфигурации {configurationId}, templateId: {templateId}", updatingStatus, configuration.Id, configuration.TemplateId); + _logger.LogInformation("Установлен статус {newStatus} для конфигурации {configurationId}, templateId: {templateId}", updatingStatus, configuration.Id, configuration.TemplateId); return true; } @@ -154,7 +154,7 @@ namespace PARR.DAL.Repositories if (config == null) { - logger.LogError($"У шаблона нет конфигурации роботов. TemplateId: {template.Id}"); + _logger.LogError($"У шаблона нет конфигурации роботов. TemplateId: {template.Id}"); throw new Exception($"У шаблона нет конфигурации роботов. TemplateId: {template.Id}"); } @@ -170,60 +170,72 @@ namespace PARR.DAL.Repositories var endDate = DateTimeOffset.UtcNow.Add(-robotWaitTime); - var configObjs = await EntitySet.Where(t => + var expiredConfigs = await EntitySet.Where(t => t.RobotStatusCode == (int)RobotStatusEnum.InProgress && t.AttemptsNumber >= robotAttemptsNumber && t.LastRobotStatusUpdated <= endDate ).ToListAsync(); - if (!configObjs.Any()) + if (!expiredConfigs.Any()) return; - configObjs.ForEach(item => + foreach (var item in expiredConfigs) { ChangeRobotStatus(RobotStatusEnum.Error, item); - logger.LogInformation($"Устанавливаю RobotStatus: {RobotStatusEnum.Error} для RobotConfigurationId {item.Id}"); - }); + _logger.LogInformation("Устанавливаю статус RobotStatus: {RobotStatus} для RobotConfigurationId: {RobotConfigurationId}", RobotStatusEnum.Error, item.Id); + } #region Ищем, есть ли связанные шаблоны, которые должны переименоваться, им тоже нужно установить статус ошибки, но только для Шаблонов - var linksCount = 0; + // Проактивная обработка связанных шаблонов переименования Old->New + // Если старый шаблон умен, мы должны сразу убить (!!!замочить!!!) и новый (целевой), чтобы он не висел вечно в ожидании. - var templateIds = configObjs.Where(t => t.RobotCode == (int)RobotsEnum.TemplateOrder).Select(t => t.TemplateId).ToList(); - if (templateIds.Any()) + var expiredTemplateIds = expiredConfigs + .Where(t => t.RobotCode == (int)RobotsEnum.TemplateOrder) + .Select(t => t.TemplateId) + .Distinct() + .ToList(); + + if (expiredTemplateIds.Any()) { - // По этим именам находим целевые шаблоны, берем их задания, и ставим им статус Ошибка + записать в лог робота - var configs = await EntitySet - .Where(t => t.RobotCode == (int)RobotsEnum.TemplateOrder - && t.RobotStatusCode != (int)RobotStatusEnum.Error - && EntityContext.Templates.Any(tmpl => - templateIds.Contains(tmpl.Id) - && tmpl.TemplateRenamePending != null - && tmpl.TemplateRenamePending.OldName == t.Template!.Name)) - .ToListAsync(); + // Находим OldName для этих шаблонов из таблицы переименований. + var oldNamesToFail = await EntityContext.Templates + .Where(t => expiredTemplateIds.Contains(t.Id) && t.TemplateRenamePending != null) + .Select(t => t.TemplateRenamePending!.OldName) + .Distinct() + .ToListAsync(); - linksCount = configs.Count; - - foreach (var item in configs) + // Находим целевые (новые задачи), имена которых совпадают с найденными OldName + if (oldNamesToFail.Any()) { - SetErrorRobotStatusAndMaxAttempts(item); + var targetConfigs = await EntitySet + .Where(t => + t.RobotCode == (int)RobotsEnum.TemplateOrder + && t.RobotStatusCode != (int)RobotStatusEnum.Error // Не трогаем те, что уже в ошибке + && oldNamesToFail.Contains(t.Template!.Name) + ).ToListAsync(); - // Пишем в лог роботу - var history = new RobotHistory + foreach (var item in targetConfigs) { - Id = Guid.NewGuid(), - HistoryLevel = (int)RobotStatusEnum.Error, - TaskStatusCode = item.TaskStatusCode, - RobotConfigurationId = item.Id, - RobotIp = null, - RobotId = ParrComponentsEnum.Api.ToString(), - RobotMessage = "[RobotConfigurationRepository] Установлен статус ошибки, так как не переименован связанный шаблон" - }; + SetErrorRobotStatusAndMaxAttempts(item); - // Синхронный Add работает быстрее и безопаснее внутри цикла - EntityContext.RobotHistories.Add(history); + // Пишем в лог роботу + var history = new RobotHistory + { + Id = Guid.NewGuid(), + HistoryLevel = (int)RobotStatusEnum.Error, + TaskStatusCode = item.TaskStatusCode, + RobotConfigurationId = item.Id, + RobotIp = null, + RobotId = ParrComponentsEnum.Api.ToString(), + RobotMessage = "[RobotConfigurationRepository] Установлен статус ошибки, так как не переименован связанный шаблон" + }; - logger.LogInformation($"Устанавливаю RobotStatus: {RobotStatusEnum.Error} для RobotConfigurationId {item.Id}"); + // Синхронный Add работает быстрее и безопаснее внутри цикла + EntityContext.RobotHistories.Add(history); + + _logger.LogInformation("Проактивно установлен статус {Status} для целевого задания RobotConfigurationID: {Id} из-за ошибки старого шаблона.", RobotStatusEnum.Error, item.Id); + } } } #endregion @@ -231,7 +243,7 @@ namespace PARR.DAL.Repositories var result = await CommitAsync(); if (!result) - logger.LogError("Ошибка при сохранении изменений RobotStatus для просроченных заданий в количестве {Count} шт.", configObjs.Count + linksCount); + _logger.LogError("Ошибка при сохранении изменений RobotStatus для просроченных заданий. Откат транзакции."); //else // logger.LogInformation("Успешно обработано и переведено в статус Ошибки просроченных заданий: {Count} шт.", configObjs.Count + linksCount); diff --git a/PARR.DAL/Repositories/TemplateRepository.cs b/PARR.DAL/Repositories/TemplateRepository.cs index 4a81b015..44b4661e 100644 --- a/PARR.DAL/Repositories/TemplateRepository.cs +++ b/PARR.DAL/Repositories/TemplateRepository.cs @@ -16,7 +16,7 @@ namespace PARR.DAL.Repositories public async Task GetTemplateByNameAsync(string name) { - logger.LogDebug("Поиск шаблона по имени: {TemplateName}", name); + _logger.LogDebug("Поиск шаблона по имени: {TemplateName}", name); var template = await GetWithIncludes() .Include(t => t.RobotConfigurations) @@ -24,11 +24,11 @@ namespace PARR.DAL.Repositories if (template != null) { - logger.LogDebug("Шаблон найден: {TemplateId}, имя: {TemplateName}", template.Id, template.Name); + _logger.LogDebug("Шаблон найден: {TemplateId}, имя: {TemplateName}", template.Id, template.Name); } else { - logger.LogDebug("Шаблон с именем {TemplateName} не найден", name); + _logger.LogDebug("Шаблон с именем {TemplateName} не найден", name); } return template; @@ -36,7 +36,7 @@ namespace PARR.DAL.Repositories public IQueryable