feat(dal, templateMatcher): небольшое улучшение быстродействия синхронизации, ShortcodesService добавлен проброс unitInTemplates для корректного подстановки шорткода %МАКС:...%

This commit is contained in:
Mikhail Kuznetsov
2026-01-23 11:29:42 +10:00
parent 9e84ef8029
commit 23e6b9f308
4 changed files with 133 additions and 133 deletions

View File

@@ -5,6 +5,7 @@ using PARR.BLL.Services.Interfaces;
using PARR.Common.Domain;
using PARR.Constants;
using PARR.DAL.Cache.Models;
using PARR.DAL.DomainServices.Interfaces;
using PARR.DAL.DomainServices.UnitFilterService;
using PARR.DAL.Models;
@@ -14,10 +15,9 @@ using PARR.DAL.NextRunServices;
using PARR.DAL.Services.Interfaces;
using PARR.DAL.Services.Interfaces.Job;
using PARR.DAL.Services.Interfaces.Unit;
using PARR.DAL.TransformServices;
using PARR.TemplateMatcher.Services.Interfaces;
using PARR.TemplateMatcher.Settings;
using System.Text.Json;
namespace PARR.TemplateMatcher.Services.Implementations;
@@ -36,7 +36,6 @@ internal class GroupedTemplateSynchronizer : ITemplateSynchronizer
private readonly ITemplateService templateService;
private readonly IJobGroupService jobGroupService;
private readonly ITemplateReuser templateReuser;
//private readonly IEsppScheduleTransformService esppScheduleTransformService;
private readonly IUnitRegionalEkPtkGroupService regionalEkPtkGroupService;
private readonly IUnitFieldService unitFieldService;
private readonly ITemplateDeactivator templateDeactivator;
@@ -56,7 +55,6 @@ internal class GroupedTemplateSynchronizer : ITemplateSynchronizer
ITemplateService templateService,
IJobGroupService jobGroupService,
ITemplateReuser templateReuser,
//IEsppScheduleTransformService esppScheduleTransformService,
IUnitRegionalEkPtkGroupService regionalEkPtkGroupService,
IUnitFieldService unitFieldService,
ITemplateDeactivator templateDeactivator,
@@ -76,7 +74,6 @@ internal class GroupedTemplateSynchronizer : ITemplateSynchronizer
this.templateService = templateService;
this.jobGroupService = jobGroupService;
this.templateReuser = templateReuser;
//this.esppScheduleTransformService = esppScheduleTransformService;
this.regionalEkPtkGroupService = regionalEkPtkGroupService;
this.unitFieldService = unitFieldService;
this.templateDeactivator = templateDeactivator;
@@ -197,12 +194,14 @@ internal class GroupedTemplateSynchronizer : ITemplateSynchronizer
var groupingFieldId = jobGroup.GroupingUnitFieldId.Value;
// Фильтруем юниты по GroupingUnitFieldId используя данные из DTO
var unitIdsWithValidGroupingFieldSet = filteredUnits
.Where(u => u.Values.Any(v =>
v.FieldId == groupingFieldId &&
!string.IsNullOrEmpty(v.Value)))
.Select(u => u.Id)
// --- ОПТИМИЗАЦИЯ: Загрузка значений поля GroupingUnitFieldId отдельно ---
logger.LogDebug("Загружаем значения поля GroupingUnitFieldId (FieldId={FieldId}) для {Count} юнитов.", groupingFieldId, expectedUnitIds.Count);
var groupingUnitValues = await unitInValueService.GetByUnitIdsAndFieldIdsAsync(expectedUnitIds, new HashSet<Guid> { groupingFieldId });
var unitIdsWithValidGroupingFieldSet = groupingUnitValues
.Where(uv => uv.Value != null && !string.IsNullOrEmpty(uv.Value.Value))
.Select(uv => uv.UnitId)
.ToList();
logger.LogDebug("После фильтрации по GroupingUnitFieldId осталось {Count} юнитов.", unitIdsWithValidGroupingFieldSet.Count);
@@ -214,13 +213,17 @@ internal class GroupedTemplateSynchronizer : ITemplateSynchronizer
return;
}
// Аналогично для фильтрации по "РАБОЧАЯ_ГР_ОТВ_ЗАК"
var unitIdsWithValidWorkGroupFieldSet = filteredUnits
.Where(u => u.Values.Any(v =>
v.FieldId == workGroupFieldId &&
relationshipGroupValueIds.Contains(v.FieldId) &&
!string.IsNullOrEmpty(v.Value)))
.Select(u => u.Id)
// --- Аналогично для фильтрации по "РАБОЧАЯ_ГР_ОТВ_ЗАК" ---
// var workGroupFieldId = workGroupField.Id; // УЖЕ ОПРЕДЕЛЕНО РАНЕЕ (строка 157)
// var relationshipGroupValueIds = regionalEkPtkGroupService.Get() // УДАЛИТЬ - УЖЕ ОПРЕДЕЛЕНО (строка 161)
logger.LogDebug("Загружаем значения поля 'РАБОЧАЯ_ГР_ОТВ_ЗАК' (FieldId={FieldId}) для {Count} юнитов.", workGroupFieldId, unitIdsWithValidGroupingFieldSet.Count);
var workGroupValues = await unitInValueService.GetByUnitIdsAndFieldIdsAsync(unitIdsWithValidGroupingFieldSet, new HashSet<Guid> { workGroupFieldId });
var unitIdsWithValidWorkGroupFieldSet = workGroupValues
.Where(uv => uv.Value != null && relationshipGroupValueIds.Contains(uv.Value.Id))
.Select(uv => uv.UnitId)
.ToList();
logger.LogDebug("После фильтрации по 'РАБОЧАЯ_ГР_ОТВ_ЗАК' осталось {Count} юнитов.", unitIdsWithValidWorkGroupFieldSet.Count);
@@ -232,41 +235,43 @@ internal class GroupedTemplateSynchronizer : ITemplateSynchronizer
return;
}
unitIdsWithValidGroupingFieldSet = unitIdsWithValidWorkGroupFieldSet;
// --- ИСПОЛЬЗУЕМ ПОСЛЕДНИЙ РЕЗУЛЬТАТ ДАЛЬШЕ ---
var finalUnitIds = unitIdsWithValidWorkGroupFieldSet; // Более понятное имя
// 5. Получить RelationshipFilters из maxJob
var relationshipFilters = maxJob.UnitFilters?.SelectMany(uf => uf.RelationshipFilters).ToList() ?? new List<JobRelationshipFilter>();
logger.LogDebug("Получение связей UnitInUnit для юнитов, прошедших фильтрацию...");
// Используем unitIdsWithValidGroupingFieldSet для получения связей
var potentialUnitInUnitLinks = await unitInUnitService.Get()
.AsNoTracking()
.Where(link => unitIdsWithValidGroupingFieldSet.Contains(link.ParentUnitId) || unitIdsWithValidGroupingFieldSet.Contains(link.ChildUnitId))
.Where(link => finalUnitIds.Contains(link.ParentUnitId) || finalUnitIds.Contains(link.ChildUnitId))
.ToListAsync();
logger.LogDebug("Найдено {Count} потенциальных связей UnitInUnit.", potentialUnitInUnitLinks.Count);
// Подготавливаем данные для фильтрации связей
var allParentIds = potentialUnitInUnitLinks.Select(l => l.ParentUnitId).ToHashSet();
var allChildIds = potentialUnitInUnitLinks.Select(l => l.ChildUnitId).ToHashSet();
// Фильтруем юниты для получения родительских и дочерних значений
var parentUnits = filteredUnits.Where(u => allParentIds.Contains(u.Id)).ToList();
var childUnits = filteredUnits.Where(u => allChildIds.Contains(u.Id)).ToList();
// --- Оптимизация: Загрузка всех значений за ОДИН запрос ---
var allRelevantUnitIds = allParentIds.Concat(allChildIds).ToHashSet();
// Создаем словари значений для родителей и детей
var parentValuesMap = parentUnits
.ToDictionary(
u => u.Id,
u => u.Values.ToDictionary(v => v.FieldId, v => v.Value)
);
var allUnitValues = await unitInValueService.Get()
.AsNoTracking()
.Include(uv => uv.Field)
.Include(uv => uv.Value)
.Where(uv => allRelevantUnitIds.Contains(uv.UnitId))
.ToListAsync();
var childValuesMap = childUnits
.ToDictionary(
u => u.Id,
u => u.Values.ToDictionary(v => v.FieldId, v => v.Value)
);
// --- Создание карт значений из одного списка ---
var parentValuesMap = allUnitValues
.Where(uv => allParentIds.Contains(uv.UnitId))
.GroupBy(uv => uv.UnitId)
.ToDictionary(g => g.Key, g => g.ToList());
var childValuesMap = allUnitValues
.Where(uv => allChildIds.Contains(uv.UnitId))
.GroupBy(uv => uv.UnitId)
.ToDictionary(g => g.Key, g => g.ToList());
logger.LogDebug("Применение {Count} RelationshipFilters к найденным связям.", relationshipFilters.Count);
@@ -276,37 +281,15 @@ internal class GroupedTemplateSynchronizer : ITemplateSynchronizer
bool linkMatchesAllFilters = true;
foreach (var rf in relationshipFilters)
{
bool filterMatch;
if (rf.IsParent)
{
// Родительские значения
if (parentValuesMap.TryGetValue(link.ParentUnitId, out var parentValues) &&
parentValues.TryGetValue(rf.FieldId, out var parentValue) &&
parentValue != null)
{
filterMatch = parentValue.Contains(rf.ValueMask ?? "", StringComparison.OrdinalIgnoreCase);
}
else
{
filterMatch = false;
}
}
else
{
// Дочерние значения
if (childValuesMap.TryGetValue(link.ChildUnitId, out var childValues) &&
childValues.TryGetValue(rf.FieldId, out var childValue) &&
childValue != null)
{
filterMatch = childValue.Contains(rf.ValueMask ?? "", StringComparison.OrdinalIgnoreCase);
}
else
{
filterMatch = false;
}
}
var valuesToCheck = rf.IsParent ? parentValuesMap.GetValueOrDefault(link.ParentUnitId, new List<UnitInValue>()) : childValuesMap.GetValueOrDefault(link.ChildUnitId, new List<UnitInValue>());
bool filterMatch = valuesToCheck.Any(uv =>
uv.FieldId == rf.FieldId &&
uv.Value != null &&
uv.Value.Value != null &&
uv.Value.Value.Contains(rf.ValueMask ?? "", StringComparison.OrdinalIgnoreCase)
);
// Учитываем инверсию фильтра
if (rf.IsInverse)
filterMatch = !filterMatch;
@@ -332,7 +315,7 @@ internal class GroupedTemplateSynchronizer : ITemplateSynchronizer
var parentUnitId = link.ParentUnitId;
var childUnitId = link.ChildUnitId;
if (unitIdsWithValidGroupingFieldSet.Contains(parentUnitId))
if (finalUnitIds.Contains(parentUnitId))
{
if (!groupedRelationships.ContainsKey(childUnitId))
{
@@ -340,7 +323,7 @@ internal class GroupedTemplateSynchronizer : ITemplateSynchronizer
}
groupedRelationships[childUnitId].Add(parentUnitId);
}
else if (unitIdsWithValidGroupingFieldSet.Contains(childUnitId))
else if (finalUnitIds.Contains(childUnitId))
{
if (!groupedRelationships.ContainsKey(parentUnitId))
{
@@ -375,10 +358,9 @@ internal class GroupedTemplateSynchronizer : ITemplateSynchronizer
.Distinct()
.ToList();
// Создаем словарь имен для конфликтующих ключей из отфильтрованных юнитов
var unitNamesMap = filteredUnits
var unitNamesMap = await unitService.Get()
.Where(u => conflictKeys.Contains(u.Id))
.ToDictionary(u => u.Id, u => u.Name);
.ToDictionaryAsync(u => u.Id, u => u.Name ?? string.Empty);
foreach (var conflictedUnitEntry in unitToKeys.Where(kvp => kvp.Value.Count > 1))
{
@@ -430,10 +412,10 @@ internal class GroupedTemplateSynchronizer : ITemplateSynchronizer
logger.LogDebug("Обработка связанного юнита {RelationshipUnitId} с {Count} юнитами из списка.", relationshipUnitId, childUnitIds.Count);
// Получаем имена юнитов из отфильтрованных данных
var childUnitNameMap = filteredUnits
var childUnitNameMap = await unitService.Get()
.AsNoTracking()
.Where(u => childUnitIds.Contains(u.Id))
.ToDictionary(u => u.Id, u => u.Name);
.ToDictionaryAsync(u => u.Id, u => u.Name);
var sortedChildUnitIds = childUnitIds
.OrderBy(id => childUnitNameMap.GetValueOrDefault(id, id.ToString()))
@@ -480,11 +462,10 @@ internal class GroupedTemplateSynchronizer : ITemplateSynchronizer
// === 2. Сравниваем детерминированно с сортировкой по имени ===
var allUnitIdsForSort = currentUnitIds.Concat(proposedUnitIds).Distinct().ToList();
// Получаем имена юнитов из отфильтрованных данных
var unitNamesForSort = filteredUnits
var unitNamesForSort = await unitService.Get()
.AsNoTracking()
.Where(u => allUnitIdsForSort.Contains(u.Id))
.ToDictionary(u => u.Id, u => u.Name);
.ToDictionaryAsync(u => u.Id, u => u.Name ?? u.Id.ToString());
var sortedCurrentUnitIds = currentUnitIds
.OrderBy(id => unitNamesForSort.GetValueOrDefault(id, id.ToString()))
@@ -505,7 +486,6 @@ internal class GroupedTemplateSynchronizer : ITemplateSynchronizer
if (!string.Equals(existingTemplateForSubGroup.Name, expectedName, StringComparison.OrdinalIgnoreCase))
{
logger.LogDebug("Шаблон {TemplateId} требует обновления имени.", existingTemplateForSubGroup.Id);
//var nextRun = await GetNextRunAsync(targetJob, existingTemplateForSubGroup.NextRun);
var nextRun = await nextRunService.GetNextRunForTemplateAsync(existingTemplateForSubGroup.Id, false);
var updateRequest = new TemplateUpdaterMq
{
@@ -560,7 +540,6 @@ internal class GroupedTemplateSynchronizer : ITemplateSynchronizer
};
var expectedName = await templateNameNormalizer.GetNormalizedTemplateNameAsync(tempTemplateForName);
//var nextRun = await GetNextRunAsync(targetJob);
var nextRun = await nextRunService.GetNextRunForTemplateAsync(reusableTemplate.Id, true);
var updateRequest = new TemplateUpdaterMq
@@ -595,12 +574,10 @@ internal class GroupedTemplateSynchronizer : ITemplateSynchronizer
{
var relationshipUnitId = kvp.Key;
var childUnitIds = kvp.Value;
// Получаем имена юнитов из отфильтрованных данных
var childUnitNameMapForDeactivate = filteredUnits
var childUnitNameMapForDeactivate = await unitService.Get()
.AsNoTracking()
.Where(u => childUnitIds.Contains(u.Id))
.ToDictionary(u => u.Id, u => u.Name);
.ToDictionaryAsync(u => u.Id, u => u.Name);
var sortedChildUnitIdsForDeactivate = childUnitIds
.OrderBy(id => childUnitNameMapForDeactivate.GetValueOrDefault(id, id.ToString()))
.ToList();
@@ -711,7 +688,6 @@ internal class GroupedTemplateSynchronizer : ITemplateSynchronizer
};
var expectedName = await templateNameNormalizer.GetNormalizedTemplateNameAsync(tempTemplateForName);
//var nextRun = await GetNextRunAsync(targetJob, template.NextRun);
var nextRun = await nextRunService.GetNextRunForTemplateAsync(template.Id, false);
var updateRequest = new TemplateUpdaterMq
{
@@ -745,18 +721,14 @@ internal class GroupedTemplateSynchronizer : ITemplateSynchronizer
HistoryInitiator = initiator
};
var msg = JsonSerializer.Serialize(mqRequest);
var result = await mqService.SendAsync(mqSettings.TemplateGenerator, new[] { msg });
//var msg = JsonSerializer.Serialize(mqRequest);
//var result = await mqService.SendAsync(mqSettings.TemplateGenerator, new[] { msg });
var result = await mqService.SendAsync(mqSettings.TemplateGenerator, new List<object> { mqRequest });
if (!result.IsSuccess)
logger.LogError("Ошибка отправки команды создания группового шаблона для Job {JobId}, связанного юнита {RelationshipUnitId}, Index {Index}.", jobId, relationshipUnitId, index);
}
//private async Task<DateTimeOffset> GetNextRunAsync(Job targetJob, DateTimeOffset? currentNextRun = null)
//{
// var referenceDate = targetJob.Group?.ReferenceDate ?? DateTimeOffset.UtcNow;
// return await esppScheduleTransformService.GetNextDateAsync(targetJob.GroupId, referenceDate);
//}
private async Task UpdateMatchingStatusAsync(Guid jobGroupId, string comment)
{