fix(dal): UnitFilter восставновлена логика по фильтрации свзяанных ЭК, в части работы параметра IsFullMatch
This commit is contained in:
@@ -340,8 +340,7 @@ internal class UnitFilterService : IUnitFilterService
|
||||
private async Task<List<FilteredUnitContext>> ApplyRelationshipFiltersOnDbAsync(
|
||||
List<Guid> unitIds,
|
||||
IEnumerable<JobRelationshipFilter> relationshipFilters,
|
||||
CancellationToken cancellationToken = default
|
||||
)
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (!relationshipFilters.Any() || !unitIds.Any())
|
||||
{
|
||||
@@ -353,178 +352,87 @@ internal class UnitFilterService : IUnitFilterService
|
||||
logger.LogDebug("ApplyRelationshipFiltersOnDbAsync: вход {UnitCount} юнитов, фильтров: {FilterCount}",
|
||||
unitIds.Count, relationshipFilters.Count());
|
||||
|
||||
#if DEBUG
|
||||
if (unitIds.Contains(targetUnitId))
|
||||
{
|
||||
logger.LogDebug("DEBUG: Юнит {TargetUnitId} присутствует во входных данных ApplyRelationshipFiltersOnDbAsync", targetUnitId);
|
||||
}
|
||||
#endif
|
||||
var resultContexts = unitIds.ToDictionary(id => id, id => new FilteredUnitContext { UnitId = id });
|
||||
|
||||
var parentRelFilters = relationshipFilters.Where(rf => rf.IsParent).ToList();
|
||||
var childRelFilters = relationshipFilters.Where(rf => !rf.IsParent).ToList();
|
||||
|
||||
var resultContexts = unitIds.ToDictionary(id => id, id => new FilteredUnitContext { UnitId = id });
|
||||
|
||||
// Обработка родительских фильтров
|
||||
if (parentRelFilters.Any())
|
||||
{
|
||||
logger.LogDebug(" Родительские фильтры ({Count}):", parentRelFilters.Count);
|
||||
|
||||
var allParentLinks = await unitInUnitService.Get()
|
||||
.AsNoTracking()
|
||||
.Where(link => unitIds.Contains(link.ChildUnitId))
|
||||
.Select(link => new { ChildId = link.ChildUnitId, ParentId = link.ParentUnitId })
|
||||
.ToListAsync(cancellationToken);
|
||||
|
||||
var allParentIds = allParentLinks.Select(l => l.ParentId).Distinct().ToList();
|
||||
logger.LogDebug(" Найдено {ParentCount} уникальных родителей для {LinkCount} связей",
|
||||
allParentIds.Count, allParentLinks.Count);
|
||||
|
||||
var validParentIds = new HashSet<Guid>(allParentIds);
|
||||
logger.LogDebug(" Начальное количество родителей: {Count}", validParentIds.Count);
|
||||
|
||||
foreach (var relFilter in parentRelFilters)
|
||||
{
|
||||
var valueMask = relFilter.ValueMask?.Trim();
|
||||
if (string.IsNullOrWhiteSpace(valueMask))
|
||||
continue;
|
||||
|
||||
bool isStartsWith = valueMask.EndsWith("%") && !valueMask.EndsWith("%%");
|
||||
bool isEndsWith = valueMask.StartsWith("%") && !valueMask.StartsWith("%%");
|
||||
string dbValueMask;
|
||||
if (isStartsWith && isEndsWith)
|
||||
dbValueMask = $"%{valueMask.Trim('%')}%";
|
||||
else if (isStartsWith)
|
||||
dbValueMask = $"{valueMask.TrimEnd('%')}%";
|
||||
else if (isEndsWith)
|
||||
dbValueMask = $"%{valueMask.TrimStart('%')}";
|
||||
else
|
||||
dbValueMask = valueMask;
|
||||
|
||||
var fieldName = relFilter.UnitField?.AihitName ?? $"FieldId={relFilter.FieldId}";
|
||||
logger.LogDebug(" RelationshipFilter (Parent): Поле='{FieldName}', Маска='{Mask}', IsInverse={IsInverse}, IsFullMatch={IsFullMatch}",
|
||||
fieldName, dbValueMask, relFilter.IsInverse, relFilter.IsFullMatch);
|
||||
|
||||
var matchingParents = await unitService.Get()
|
||||
.AsNoTracking()
|
||||
.Where(u => allParentIds.Contains(u.Id))
|
||||
.Where(u => u.UnitValues.Any(v =>
|
||||
v.FieldId == relFilter.FieldId &&
|
||||
EF.Functions.Like(v.Value.Value, dbValueMask)))
|
||||
.Select(u => u.Id)
|
||||
.ToListAsync(cancellationToken);
|
||||
|
||||
logger.LogDebug(" Найдено {MatchCount} родителей по маске", matchingParents.Count);
|
||||
|
||||
if (relFilter.IsInverse)
|
||||
{
|
||||
validParentIds.ExceptWith(matchingParents);
|
||||
logger.LogDebug(" IsInverse=true → исключены {ExcludedCount} родителей, осталось {RemainingCount}",
|
||||
matchingParents.Count, validParentIds.Count);
|
||||
}
|
||||
else
|
||||
{
|
||||
validParentIds.IntersectWith(matchingParents);
|
||||
logger.LogDebug(" IsInverse=false → оставлены {KeptCount} родителей",
|
||||
validParentIds.Count);
|
||||
}
|
||||
}
|
||||
|
||||
// Сопоставляем подходящих родителей с их детьми
|
||||
foreach (var link in allParentLinks)
|
||||
{
|
||||
if (validParentIds.Contains(link.ParentId))
|
||||
{
|
||||
resultContexts[link.ChildId].ValidParentIds.Add(link.ParentId);
|
||||
}
|
||||
}
|
||||
|
||||
var totalParentsAdded = resultContexts.Values.Sum(c => c.ValidParentIds.Count);
|
||||
logger.LogDebug(" Добавлено {TotalParents} родительских связей для {UnitCount} юнитов",
|
||||
totalParentsAdded, resultContexts.Count);
|
||||
await ApplyRelationshipFiltersAsync(
|
||||
unitIds,
|
||||
resultContexts, // ← Передаём словарь, а не список
|
||||
parentRelFilters,
|
||||
isParentDirection: true,
|
||||
cancellationToken);
|
||||
}
|
||||
|
||||
// Обработка дочерних фильтров
|
||||
if (childRelFilters.Any())
|
||||
{
|
||||
logger.LogDebug(" Дочерние фильтры ({Count}):", childRelFilters.Count);
|
||||
|
||||
var allChildLinks = await unitInUnitService.Get()
|
||||
.AsNoTracking()
|
||||
.Where(link => unitIds.Contains(link.ParentUnitId))
|
||||
.Select(link => new { ParentId = link.ParentUnitId, ChildId = link.ChildUnitId })
|
||||
.ToListAsync(cancellationToken);
|
||||
|
||||
var allChildIds = allChildLinks.Select(l => l.ChildId).Distinct().ToList();
|
||||
logger.LogDebug(" Найдено {ChildCount} уникальных детей для {LinkCount} связей",
|
||||
allChildIds.Count, allChildLinks.Count);
|
||||
|
||||
var validChildIds = new HashSet<Guid>(allChildIds);
|
||||
logger.LogDebug(" Начальное количество детей: {Count}", validChildIds.Count);
|
||||
|
||||
foreach (var relFilter in childRelFilters)
|
||||
{
|
||||
var valueMask = relFilter.ValueMask?.Trim();
|
||||
if (string.IsNullOrWhiteSpace(valueMask))
|
||||
continue;
|
||||
|
||||
bool isStartsWith = valueMask.EndsWith("%") && !valueMask.EndsWith("%%");
|
||||
bool isEndsWith = valueMask.StartsWith("%") && !valueMask.StartsWith("%%");
|
||||
string dbValueMask;
|
||||
if (isStartsWith && isEndsWith)
|
||||
dbValueMask = $"%{valueMask.Trim('%')}%";
|
||||
else if (isStartsWith)
|
||||
dbValueMask = $"{valueMask.TrimEnd('%')}%";
|
||||
else if (isEndsWith)
|
||||
dbValueMask = $"%{valueMask.TrimStart('%')}";
|
||||
else
|
||||
dbValueMask = valueMask;
|
||||
|
||||
var fieldName = relFilter.UnitField?.AihitName ?? $"FieldId={relFilter.FieldId}";
|
||||
logger.LogDebug(" RelationshipFilter (Child): Поле='{FieldName}', Маска='{Mask}', IsInverse={IsInverse}, IsFullMatch={IsFullMatch}",
|
||||
fieldName, dbValueMask, relFilter.IsInverse, relFilter.IsFullMatch);
|
||||
|
||||
var matchingChildren = await unitService.Get()
|
||||
.AsNoTracking()
|
||||
.Where(u => allChildIds.Contains(u.Id))
|
||||
.Where(u => u.UnitValues.Any(v =>
|
||||
v.FieldId == relFilter.FieldId &&
|
||||
EF.Functions.Like(v.Value.Value, dbValueMask)))
|
||||
.Select(u => u.Id)
|
||||
.ToListAsync(cancellationToken);
|
||||
|
||||
logger.LogDebug(" Найдено {MatchCount} детей по маске", matchingChildren.Count);
|
||||
|
||||
if (relFilter.IsInverse)
|
||||
{
|
||||
validChildIds.ExceptWith(matchingChildren);
|
||||
logger.LogDebug(" IsInverse=true → исключены {ExcludedCount} детей, осталось {RemainingCount}",
|
||||
matchingChildren.Count, validChildIds.Count);
|
||||
}
|
||||
else
|
||||
{
|
||||
validChildIds.IntersectWith(matchingChildren);
|
||||
logger.LogDebug(" IsInverse=false → оставлены {KeptCount} детей",
|
||||
validChildIds.Count);
|
||||
}
|
||||
}
|
||||
|
||||
// Сопоставляем подходящих детей с их родителями
|
||||
foreach (var link in allChildLinks)
|
||||
{
|
||||
if (validChildIds.Contains(link.ChildId))
|
||||
{
|
||||
resultContexts[link.ParentId].ValidChildIds.Add(link.ChildId);
|
||||
}
|
||||
}
|
||||
|
||||
var totalChildrenAdded = resultContexts.Values.Sum(c => c.ValidChildIds.Count);
|
||||
logger.LogDebug(" Добавлено {TotalChildren} дочерних связей для {UnitCount} юнитов",
|
||||
totalChildrenAdded, resultContexts.Count);
|
||||
await ApplyRelationshipFiltersAsync(
|
||||
unitIds,
|
||||
resultContexts, // ← Передаём словарь, а не список
|
||||
childRelFilters,
|
||||
isParentDirection: false,
|
||||
cancellationToken);
|
||||
}
|
||||
|
||||
var result = resultContexts.Values.ToList();
|
||||
logger.LogDebug("ApplyRelationshipFiltersOnDbAsync: выход {ContextCount} контекстов", result.Count);
|
||||
// Если фильтры были — оставляем только юниты со связями
|
||||
// Если фильтров не было — оставляем все юниты
|
||||
bool hasRelationshipFilters = parentRelFilters.Any() || childRelFilters.Any();
|
||||
|
||||
var unitsWithRelationships = hasRelationshipFilters
|
||||
? resultContexts.Values.Where(c => c.ValidParentIds.Any() || c.ValidChildIds.Any()).ToList()
|
||||
: resultContexts.Values.ToList();
|
||||
|
||||
var result = unitsWithRelationships;
|
||||
logger.LogDebug("ApplyRelationshipFiltersOnDbAsync: выход {ContextCount} контекстов (из {InitialCount})",
|
||||
result.Count, resultContexts.Count);
|
||||
|
||||
#if DEBUG
|
||||
foreach (var context in result)
|
||||
{
|
||||
var u = await unitService.GetAsync(context.UnitId);
|
||||
|
||||
if (u == null)
|
||||
continue;
|
||||
|
||||
logger.LogDebug("Найден ЭК {UnitName}", u.Name);
|
||||
|
||||
if (context.ValidParentIds.Any())
|
||||
{
|
||||
|
||||
var parents = context.ValidParentIds;
|
||||
|
||||
logger.LogDebug("\tФильтрам соответствуют {ParentsCount} родителей:", parents.Count);
|
||||
|
||||
foreach (var parent in context.ValidParentIds)
|
||||
{
|
||||
var p = await unitService.GetAsync(parent);
|
||||
|
||||
logger.LogDebug("\t\t {ParentName};", p?.Name);
|
||||
}
|
||||
}
|
||||
|
||||
if (context.ValidChildIds.Any())
|
||||
{
|
||||
|
||||
var children = context.ValidChildIds;
|
||||
|
||||
logger.LogDebug("\tФильтрам соответствуют {ParentsCount} детей:", children.Count);
|
||||
|
||||
foreach (var child in children)
|
||||
{
|
||||
var c = await unitService.GetAsync(child);
|
||||
|
||||
logger.LogDebug("\t\t {ChildName};", c?.Name);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
return result;
|
||||
}
|
||||
@@ -694,7 +602,6 @@ internal class UnitFilterService : IUnitFilterService
|
||||
}
|
||||
|
||||
|
||||
|
||||
public async Task<List<string>> GetRelatedUnitNamesAsync(Guid jobId, Guid unitId, CancellationToken cancellationToken = default)
|
||||
{
|
||||
logger.LogDebug("Начало GetRelatedUnitNamesAsync. JobId: {JobId}, UnitId: {UnitId}", jobId, unitId);
|
||||
@@ -785,6 +692,172 @@ internal class UnitFilterService : IUnitFilterService
|
||||
}
|
||||
|
||||
#region вспомогательные методы
|
||||
/// <summary>
|
||||
/// Оценивает, проходит ли юнит фильтр связей
|
||||
/// </summary>
|
||||
private bool EvaluateFilter(
|
||||
List<Guid> unitRelations, // Все связи юнита (родители или дети)
|
||||
List<Guid> matchingRelations, // Связи, которые соответствуют маске
|
||||
bool isFullMatch, // Полное совпадение (All) или хотя бы одно (Any)
|
||||
bool isInverse, // Обратный фильтр
|
||||
bool hasRelations // Есть ли связи у юнита
|
||||
)
|
||||
{
|
||||
int matchingCount = unitRelations.Count(r => matchingRelations.Contains(r));
|
||||
int totalCount = unitRelations.Count;
|
||||
|
||||
// Случай: нет связей в направлении
|
||||
if (!hasRelations)
|
||||
{
|
||||
// Если IsInverse = true → юнит проходит (нет связей, которые нарушают)
|
||||
// Если IsInverse = false → юнит не проходит (нет связей, которые соответствуют)
|
||||
return isInverse;
|
||||
}
|
||||
|
||||
// Основная логика
|
||||
if (isFullMatch)
|
||||
{
|
||||
// Все связи должны соответствовать
|
||||
bool allMatch = matchingCount == totalCount;
|
||||
|
||||
if (isInverse)
|
||||
//Все связи не соответствуют фильтру
|
||||
return matchingCount == 0;
|
||||
else
|
||||
//Все связи соответствуют фильтру
|
||||
return allMatch;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Хотя бы одна связь должна соответствовать
|
||||
return matchingCount > 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Применяет фильтры к связям (родителям или детям)
|
||||
/// </summary>
|
||||
private async Task ApplyRelationshipFiltersAsync(
|
||||
List<Guid> unitIds,
|
||||
Dictionary<Guid, FilteredUnitContext> resultContexts,
|
||||
List<JobRelationshipFilter> relFilters,
|
||||
bool isParentDirection,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
if (!relFilters.Any())
|
||||
return;
|
||||
|
||||
var directionName = isParentDirection ? "Родительские" : "Дочерние";
|
||||
logger.LogDebug(" {Direction} фильтры ({Count}):", directionName, relFilters.Count);
|
||||
|
||||
// Получаем все связи в нужном направлении
|
||||
var allLinks = await unitInUnitService.Get()
|
||||
.AsNoTracking()
|
||||
.Where(link => isParentDirection
|
||||
? unitIds.Contains(link.ChildUnitId)
|
||||
: unitIds.Contains(link.ParentUnitId))
|
||||
.Select(link => new
|
||||
{
|
||||
SourceId = isParentDirection ? link.ChildUnitId : link.ParentUnitId,
|
||||
TargetId = isParentDirection ? link.ParentUnitId : link.ChildUnitId
|
||||
})
|
||||
.ToListAsync(cancellationToken);
|
||||
|
||||
var allTargetIds = allLinks.Select(l => l.TargetId).Distinct().ToList();
|
||||
logger.LogDebug(" Найдено {TargetCount} уникальных {TargetType} для {LinkCount} связей",
|
||||
allTargetIds.Count,
|
||||
isParentDirection ? "родителей" : "детей",
|
||||
allLinks.Count);
|
||||
|
||||
// Группируем связи по исходному юниту для быстрого доступа
|
||||
var linksBySource = allLinks.GroupBy(l => l.SourceId)
|
||||
.ToDictionary(g => g.Key, g => g.Select(l => l.TargetId).ToList());
|
||||
|
||||
// Обрабатываем каждый фильтр
|
||||
foreach (var relFilter in relFilters)
|
||||
{
|
||||
var valueMask = relFilter.ValueMask?.Trim();
|
||||
if (string.IsNullOrWhiteSpace(valueMask))
|
||||
continue;
|
||||
|
||||
// Обработка маски LIKE
|
||||
bool isStartsWith = valueMask.EndsWith("%") && !valueMask.EndsWith("%%");
|
||||
bool isEndsWith = valueMask.StartsWith("%") && !valueMask.StartsWith("%%");
|
||||
string dbValueMask;
|
||||
if (isStartsWith && isEndsWith)
|
||||
dbValueMask = $"%{valueMask.Trim('%')}%";
|
||||
else if (isStartsWith)
|
||||
dbValueMask = $"{valueMask.TrimEnd('%')}%";
|
||||
else if (isEndsWith)
|
||||
dbValueMask = $"%{valueMask.TrimStart('%')}";
|
||||
else
|
||||
dbValueMask = valueMask;
|
||||
|
||||
var fieldName = relFilter.UnitField?.AihitName ?? $"FieldId={relFilter.FieldId}";
|
||||
logger.LogDebug(" RelationshipFilter ({Direction}): Поле='{FieldName}', Маска='{Mask}', IsInverse={IsInverse}, IsFullMatch={IsFullMatch}",
|
||||
isParentDirection ? "Parent" : "Child",
|
||||
fieldName, dbValueMask, relFilter.IsInverse, relFilter.IsFullMatch);
|
||||
|
||||
// Находим целевые юниты, которые соответствуют фильтру
|
||||
var matchingTargetIds = await unitService.Get()
|
||||
.AsNoTracking()
|
||||
.Where(u => allTargetIds.Contains(u.Id))
|
||||
.Where(u => u.UnitValues.Any(v =>
|
||||
v.FieldId == relFilter.FieldId &&
|
||||
EF.Functions.Like(v.Value.Value, dbValueMask)))
|
||||
.Select(u => u.Id)
|
||||
.ToListAsync(cancellationToken);
|
||||
|
||||
logger.LogDebug(" Найдено {MatchCount} {TargetType} по маске",
|
||||
matchingTargetIds.Count,
|
||||
isParentDirection ? "родителей" : "детей");
|
||||
|
||||
// Применяем логику к каждому юниту
|
||||
foreach (var context in resultContexts.Values)
|
||||
{
|
||||
var unitTargets = linksBySource.GetValueOrDefault(context.UnitId, new List<Guid>());
|
||||
bool hasTargets = unitTargets.Any();
|
||||
|
||||
bool passesFilter = EvaluateFilter(
|
||||
unitTargets,
|
||||
matchingTargetIds,
|
||||
relFilter.IsFullMatch,
|
||||
relFilter.IsInverse,
|
||||
hasTargets
|
||||
);
|
||||
|
||||
if (passesFilter)
|
||||
{
|
||||
var validTargets = relFilter.IsInverse
|
||||
? unitTargets.Except(matchingTargetIds)
|
||||
: unitTargets.Intersect(matchingTargetIds);
|
||||
|
||||
if (isParentDirection)
|
||||
context.ValidParentIds.UnionWith(validTargets);
|
||||
else
|
||||
context.ValidChildIds.UnionWith(validTargets);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Юнит не прошёл фильтр — очищаем его связи
|
||||
if (isParentDirection)
|
||||
context.ValidParentIds.Clear();
|
||||
else
|
||||
context.ValidChildIds.Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var totalAdded = resultContexts.Sum(c =>
|
||||
isParentDirection ? c.Value.ValidParentIds.Count : c.Value.ValidChildIds.Count);
|
||||
|
||||
logger.LogDebug(" Добавлено {Total} {TargetType} связей для {UnitCount} юнитов",
|
||||
totalAdded,
|
||||
isParentDirection ? "родительских" : "дочерних",
|
||||
resultContexts.Count);
|
||||
}
|
||||
|
||||
|
||||
private async Task<List<Guid>> GetUnitIdsFromCacheOrDbAsync(JobUnitFilter filter, CancellationToken cancellationToken = default)
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user