fix(dal): UnitFilterServicen исправлена инверсивная фильтрация связанных ЭК
This commit is contained in:
@@ -2,6 +2,6 @@
|
|||||||
{
|
{
|
||||||
internal class UnitFilterServiceOptions
|
internal class UnitFilterServiceOptions
|
||||||
{
|
{
|
||||||
public int LoadBatchSize { get; set; } = 100;
|
public int LoadBatchSize { get; set; } = 50;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ using PARR.DAL.Contracts;
|
|||||||
using PARR.DAL.DomainServices.UnitFilterService.Models;
|
using PARR.DAL.DomainServices.UnitFilterService.Models;
|
||||||
using PARR.DAL.Models.Job;
|
using PARR.DAL.Models.Job;
|
||||||
using PARR.DAL.Models.Unit;
|
using PARR.DAL.Models.Unit;
|
||||||
|
using PARR.DAL.Services.Implementations.Unit;
|
||||||
using PARR.DAL.Services.Interfaces.Job;
|
using PARR.DAL.Services.Interfaces.Job;
|
||||||
using PARR.DAL.Services.Interfaces.Unit;
|
using PARR.DAL.Services.Interfaces.Unit;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
@@ -26,6 +27,7 @@ internal class UnitFilterService : IUnitFilterService
|
|||||||
private readonly IUnitService unitService;
|
private readonly IUnitService unitService;
|
||||||
private readonly IUnitInUnitService unitInUnitService;
|
private readonly IUnitInUnitService unitInUnitService;
|
||||||
private readonly IRedisCacheService cacheService;
|
private readonly IRedisCacheService cacheService;
|
||||||
|
private readonly IUnitFieldService unitFieldService;
|
||||||
private readonly IUnitInValueService unitInValueService;
|
private readonly IUnitInValueService unitInValueService;
|
||||||
|
|
||||||
public UnitFilterService(
|
public UnitFilterService(
|
||||||
@@ -35,7 +37,8 @@ internal class UnitFilterService : IUnitFilterService
|
|||||||
IUnitInUnitService unitInUnitService,
|
IUnitInUnitService unitInUnitService,
|
||||||
IUnitInValueService unitInValueService,
|
IUnitInValueService unitInValueService,
|
||||||
IRedisCacheService cacheService,
|
IRedisCacheService cacheService,
|
||||||
IOptions<UnitFilterServiceOptions> options
|
IOptions<UnitFilterServiceOptions> options,
|
||||||
|
IUnitFieldService unitFieldService
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
this.logger = logger;
|
this.logger = logger;
|
||||||
@@ -43,6 +46,7 @@ internal class UnitFilterService : IUnitFilterService
|
|||||||
this.unitService = unitService;
|
this.unitService = unitService;
|
||||||
this.unitInUnitService = unitInUnitService;
|
this.unitInUnitService = unitInUnitService;
|
||||||
this.cacheService = cacheService;
|
this.cacheService = cacheService;
|
||||||
|
this.unitFieldService = unitFieldService;
|
||||||
this.unitInValueService = unitInValueService;
|
this.unitInValueService = unitInValueService;
|
||||||
|
|
||||||
batchSize = options.Value.LoadBatchSize;
|
batchSize = options.Value.LoadBatchSize;
|
||||||
@@ -138,7 +142,7 @@ internal class UnitFilterService : IUnitFilterService
|
|||||||
|
|
||||||
// 3. Применить RelationshipFilters на уровне SQL -> ВОЗВРАЩАЕТ FilteredUnitContext
|
// 3. Применить RelationshipFilters на уровне SQL -> ВОЗВРАЩАЕТ FilteredUnitContext
|
||||||
var relStopwatch = Stopwatch.StartNew();
|
var relStopwatch = Stopwatch.StartNew();
|
||||||
var relationshipFilteredContexts = await ApplyRelationshipFiltersOnDbAsync(fieldFilteredIds, filter.RelationshipFilters, cancellationToken);
|
var relationshipFilteredContexts = await ProcessRelationshipFiltersAsync(fieldFilteredIds, filter.RelationshipFilters, cancellationToken);
|
||||||
relStopwatch.Stop();
|
relStopwatch.Stop();
|
||||||
|
|
||||||
if (!relationshipFilteredContexts.Any())
|
if (!relationshipFilteredContexts.Any())
|
||||||
@@ -306,22 +310,14 @@ internal class UnitFilterService : IUnitFilterService
|
|||||||
{
|
{
|
||||||
query = query.Where(u => !u.UnitValues.Any(v =>
|
query = query.Where(u => !u.UnitValues.Any(v =>
|
||||||
v.FieldId == fieldFilter.FieldId &&
|
v.FieldId == fieldFilter.FieldId &&
|
||||||
EF.Functions.Like(v.Value.Value, dbValueMask)));
|
EF.Functions.ILike(v.Value.Value, dbValueMask)));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
query = query.Where(u => u.UnitValues.Any(v =>
|
query = query.Where(u => u.UnitValues.Any(v =>
|
||||||
v.FieldId == fieldFilter.FieldId &&
|
v.FieldId == fieldFilter.FieldId &&
|
||||||
EF.Functions.Like(v.Value.Value, dbValueMask)));
|
EF.Functions.ILike(v.Value.Value, dbValueMask)));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Выполняем промежуточный запрос для логирования
|
|
||||||
//var intermediateResult = await query.Select(u => u.Id).ToListAsync(cancellationToken);
|
|
||||||
//logger.LogDebug(" После фильтра #{Index}: осталось {Count} юнитов", filterIndex, intermediateResult.Count);
|
|
||||||
|
|
||||||
//// Обновляем query для следующей итерации
|
|
||||||
//query = unitService.Get().AsNoTracking()
|
|
||||||
// .Where(u => intermediateResult.Contains(u.Id));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var result = await query.Select(u => u.Id).ToListAsync(cancellationToken);
|
var result = await query.Select(u => u.Id).ToListAsync(cancellationToken);
|
||||||
@@ -337,7 +333,7 @@ internal class UnitFilterService : IUnitFilterService
|
|||||||
/// <param name="unitIds"></param>
|
/// <param name="unitIds"></param>
|
||||||
/// <param name="relationshipFilters"></param>
|
/// <param name="relationshipFilters"></param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
private async Task<List<FilteredUnitContext>> ApplyRelationshipFiltersOnDbAsync(
|
private async Task<List<FilteredUnitContext>> ProcessRelationshipFiltersAsync(
|
||||||
List<Guid> unitIds,
|
List<Guid> unitIds,
|
||||||
IEnumerable<JobRelationshipFilter> relationshipFilters,
|
IEnumerable<JobRelationshipFilter> relationshipFilters,
|
||||||
CancellationToken cancellationToken = default)
|
CancellationToken cancellationToken = default)
|
||||||
@@ -360,9 +356,9 @@ internal class UnitFilterService : IUnitFilterService
|
|||||||
// Обработка родительских фильтров
|
// Обработка родительских фильтров
|
||||||
if (parentRelFilters.Any())
|
if (parentRelFilters.Any())
|
||||||
{
|
{
|
||||||
await ApplyRelationshipFiltersAsync(
|
await ApplyRelationshipFiltersOnDbAsync(
|
||||||
unitIds,
|
unitIds,
|
||||||
resultContexts, // ← Передаём словарь, а не список
|
resultContexts,
|
||||||
parentRelFilters,
|
parentRelFilters,
|
||||||
isParentDirection: true,
|
isParentDirection: true,
|
||||||
cancellationToken);
|
cancellationToken);
|
||||||
@@ -371,9 +367,9 @@ internal class UnitFilterService : IUnitFilterService
|
|||||||
// Обработка дочерних фильтров
|
// Обработка дочерних фильтров
|
||||||
if (childRelFilters.Any())
|
if (childRelFilters.Any())
|
||||||
{
|
{
|
||||||
await ApplyRelationshipFiltersAsync(
|
await ApplyRelationshipFiltersOnDbAsync(
|
||||||
unitIds,
|
unitIds,
|
||||||
resultContexts, // ← Передаём словарь, а не список
|
resultContexts,
|
||||||
childRelFilters,
|
childRelFilters,
|
||||||
isParentDirection: false,
|
isParentDirection: false,
|
||||||
cancellationToken);
|
cancellationToken);
|
||||||
@@ -392,85 +388,100 @@ internal class UnitFilterService : IUnitFilterService
|
|||||||
result.Count, resultContexts.Count);
|
result.Count, resultContexts.Count);
|
||||||
|
|
||||||
#if DEBUG
|
#if DEBUG
|
||||||
|
// Собираем все UnitId для пакетной загрузки (основные + родители + дети)
|
||||||
|
var allUnitIds = result.Select(c => c.UnitId)
|
||||||
|
.Concat(result.SelectMany(c => c.ValidParentIds))
|
||||||
|
.Concat(result.SelectMany(c => c.ValidChildIds))
|
||||||
|
.Distinct()
|
||||||
|
.Take(100)
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
// Пакетная загрузка всех юнитов
|
||||||
|
var unitsMap = await unitService.Get().AsNoTracking()
|
||||||
|
.Where(u => allUnitIds.Contains(u.Id))
|
||||||
|
.ToDictionaryAsync(u => u.Id, cancellationToken);
|
||||||
|
|
||||||
|
// Получаем все FieldId из фильтров для отображения значений
|
||||||
|
var parentFieldIds = relationshipFilters.Where(rf => rf.IsParent).Select(rf => rf.FieldId).Distinct().ToList();
|
||||||
|
var childFieldIds = relationshipFilters.Where(rf => !rf.IsParent).Select(rf => rf.FieldId).Distinct().ToList();
|
||||||
|
var allFieldIds = parentFieldIds.Concat(childFieldIds).Distinct().ToList();
|
||||||
|
|
||||||
|
// ПАКЕТНАЯ загрузка значений для ВСЕХ юнитов сразу (1 запрос вместо N+1)
|
||||||
|
var allValues = await unitInValueService.GetByUnitIdsAndFieldIdsAsync(allUnitIds, allFieldIds);
|
||||||
|
var valuesByUnit = allValues.GroupBy(v => v.UnitId)
|
||||||
|
.ToDictionary(g => g.Key, g => g.ToList());
|
||||||
|
|
||||||
|
// ПАКЕТНАЯ загрузка имён полей (1 запрос вместо N+1)
|
||||||
|
var fieldsMap = await unitFieldService.Get()
|
||||||
|
.AsNoTracking()
|
||||||
|
.Where(f => allFieldIds.Contains(f.Id))
|
||||||
|
.ToDictionaryAsync(f => f.Id, f => f.AihitName, cancellationToken);
|
||||||
|
|
||||||
foreach (var context in result.Take(10))
|
foreach (var context in result.Take(10))
|
||||||
{
|
{
|
||||||
var u = await unitService.GetAsync(context.UnitId);
|
var u = unitsMap.GetValueOrDefault(context.UnitId);
|
||||||
|
|
||||||
if (u == null)
|
if (u == null)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
logger.LogDebug("📦 Найден ЭК {UnitName} (Id={UnitId})", u.Name, u.Id);
|
logger.LogDebug("Найден ЭК {UnitName} (Id={UnitId})", u.Name, u.Id);
|
||||||
|
|
||||||
// Получаем все FieldId из фильтров для отображения значений
|
|
||||||
var parentFieldIds = relationshipFilters.Where(rf => rf.IsParent).Select(rf => rf.FieldId).Distinct().ToList();
|
|
||||||
var childFieldIds = relationshipFilters.Where(rf => !rf.IsParent).Select(rf => rf.FieldId).Distinct().ToList();
|
|
||||||
|
|
||||||
// === РОДИТЕЛИ ===
|
// === РОДИТЕЛИ ===
|
||||||
if (context.ValidParentIds.Any())
|
if (context.ValidParentIds.Any())
|
||||||
{
|
{
|
||||||
logger.LogDebug("\t📌 Фильтрам соответствуют {ParentsCount} родителей:", context.ValidParentIds.Count);
|
logger.LogDebug("\tФильтрам соответствуют {ParentsCount} родителей:", context.ValidParentIds.Count);
|
||||||
|
|
||||||
foreach (var parentId in context.ValidParentIds)
|
foreach (var parentId in context.ValidParentIds)
|
||||||
{
|
{
|
||||||
var p = await unitService.GetAsync(parentId);
|
var p = unitsMap.GetValueOrDefault(parentId);
|
||||||
logger.LogDebug("\t\t👤 {ParentName} (Id={ParentId})", p?.Name, parentId);
|
logger.LogDebug("\t\t{ParentName} (Id={ParentId})", p?.Name, parentId);
|
||||||
|
|
||||||
// Выводим значения только для полей из RelationshipFilters
|
// Берём значения из кэша, а не делаем запрос
|
||||||
if (parentFieldIds.Any())
|
if (parentFieldIds.Any() && valuesByUnit.TryGetValue(parentId, out var parentValues))
|
||||||
{
|
{
|
||||||
var parentValues = await unitInValueService.GetByUnitIdsAndFieldIdsAsync(
|
foreach (var pv in parentValues.Where(v => parentFieldIds.Contains(v.FieldId)))
|
||||||
new[] { parentId },
|
|
||||||
parentFieldIds
|
|
||||||
);
|
|
||||||
|
|
||||||
foreach (var pv in parentValues)
|
|
||||||
{
|
{
|
||||||
var field = pv.Field;
|
// Используем предварительно загруженный словарь полей
|
||||||
var value = pv.Value?.Value;
|
var fieldName = fieldsMap.GetValueOrDefault(pv.FieldId) ?? $"FieldId={pv.FieldId}";
|
||||||
logger.LogDebug("\t\t 🔹 {FieldName} = {FieldValue}",
|
logger.LogDebug("\t\t {FieldName} = {FieldValue}",
|
||||||
field?.AihitName ?? $"FieldId={pv.FieldId}",
|
fieldName,
|
||||||
value ?? "null");
|
pv.Value?.Value ?? "null");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (parentRelFilters.Any())
|
else if (parentRelFilters.Any())
|
||||||
{
|
{
|
||||||
logger.LogDebug("\t Родительские фильтры заданы, но подходящих родителей не найдено");
|
logger.LogDebug("\tРодительские фильтры заданы, но подходящих родителей не найдено");
|
||||||
}
|
}
|
||||||
|
|
||||||
// === ДЕТИ ===
|
// === ДЕТИ ===
|
||||||
if (context.ValidChildIds.Any())
|
if (context.ValidChildIds.Any())
|
||||||
{
|
{
|
||||||
logger.LogDebug("\t Фильтрам соответствуют {ChildrenCount} детей:", context.ValidChildIds.Count);
|
logger.LogDebug("\tФильтрам соответствуют {ChildrenCount} детей:", context.ValidChildIds.Count);
|
||||||
|
|
||||||
foreach (var childId in context.ValidChildIds)
|
foreach (var childId in context.ValidChildIds)
|
||||||
{
|
{
|
||||||
var c = await unitService.GetAsync(childId);
|
var c = unitsMap.GetValueOrDefault(childId);
|
||||||
logger.LogDebug("\t\t {ChildName} (Id={ChildId})", c?.Name, childId);
|
logger.LogDebug("\t\t{ChildName} (Id={ChildId})", c?.Name, childId);
|
||||||
|
|
||||||
// Выводим значения только для полей из RelationshipFilters
|
// Берём значения из кэша, а не делаем запрос
|
||||||
if (childFieldIds.Any())
|
if (childFieldIds.Any() && valuesByUnit.TryGetValue(childId, out var childValues))
|
||||||
{
|
{
|
||||||
var childValues = await unitInValueService.GetByUnitIdsAndFieldIdsAsync(
|
foreach (var cv in childValues.Where(v => childFieldIds.Contains(v.FieldId)))
|
||||||
new[] { childId },
|
|
||||||
childFieldIds
|
|
||||||
);
|
|
||||||
|
|
||||||
foreach (var cv in childValues)
|
|
||||||
{
|
{
|
||||||
var field = cv.Field;
|
// Используем предварительно загруженный словарь полей
|
||||||
var value = cv.Value?.Value;
|
var fieldName = fieldsMap.GetValueOrDefault(cv.FieldId) ?? $"FieldId={cv.FieldId}";
|
||||||
logger.LogDebug("\t\t {FieldName} = {FieldValue}",
|
logger.LogDebug("\t\t {FieldName} = {FieldValue}",
|
||||||
field?.AihitName ?? $"FieldId={cv.FieldId}",
|
fieldName,
|
||||||
value ?? "null");
|
cv.Value?.Value ?? "null");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (childRelFilters.Any())
|
else if (childRelFilters.Any())
|
||||||
{
|
{
|
||||||
logger.LogDebug("\t Дочерние фильтры заданы, но подходящих детей не найдено");
|
logger.LogDebug("\tДочерние фильтры заданы, но подходящих детей не найдено");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
@@ -733,57 +744,11 @@ internal class UnitFilterService : IUnitFilterService
|
|||||||
}
|
}
|
||||||
|
|
||||||
#region вспомогательные методы
|
#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 !allMatch; // НЕ все связи соответствуют
|
|
||||||
else
|
|
||||||
return allMatch; // ВСЕ связи соответствуют
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// ХОТЯ БЫ ОДНА связь должна соответствовать
|
|
||||||
bool anyMatch = matchingCount > 0;
|
|
||||||
|
|
||||||
if (isInverse)
|
|
||||||
return !anyMatch; // НИ ОДНА связь не соответствует
|
|
||||||
else
|
|
||||||
return anyMatch; // ХОТЯ БЫ ОДНА связь соответствует
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Применяет фильтры к связям (родителям или детям)
|
/// Применяет фильтры к связям (родителям или детям)
|
||||||
/// Связь должна пройти ВСЕ фильтры направления
|
/// Связь должна пройти ВСЕ фильтры направления
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private async Task ApplyRelationshipFiltersAsync(
|
private async Task ApplyRelationshipFiltersOnDbAsync(
|
||||||
List<Guid> unitIds,
|
List<Guid> unitIds,
|
||||||
Dictionary<Guid, FilteredUnitContext> resultContexts,
|
Dictionary<Guid, FilteredUnitContext> resultContexts,
|
||||||
List<JobRelationshipFilter> relFilters,
|
List<JobRelationshipFilter> relFilters,
|
||||||
@@ -820,7 +785,6 @@ internal class UnitFilterService : IUnitFilterService
|
|||||||
.ToDictionary(g => g.Key, g => g.Select(l => l.TargetId).ToList());
|
.ToDictionary(g => g.Key, g => g.Select(l => l.TargetId).ToList());
|
||||||
|
|
||||||
// Находим связи, которые проходят ВСЕ фильтры одновременно
|
// Находим связи, которые проходят ВСЕ фильтры одновременно
|
||||||
// Для каждого TargetId считаем, сколько фильтров он прошёл
|
|
||||||
var targetFilterPassCount = new Dictionary<Guid, int>();
|
var targetFilterPassCount = new Dictionary<Guid, int>();
|
||||||
|
|
||||||
foreach (var relFilter in relFilters)
|
foreach (var relFilter in relFilters)
|
||||||
@@ -847,13 +811,13 @@ internal class UnitFilterService : IUnitFilterService
|
|||||||
isParentDirection ? "Parent" : "Child",
|
isParentDirection ? "Parent" : "Child",
|
||||||
fieldName, dbValueMask, relFilter.IsInverse, relFilter.IsFullMatch);
|
fieldName, dbValueMask, relFilter.IsInverse, relFilter.IsFullMatch);
|
||||||
|
|
||||||
// Находим целевые юниты, которые соответствуют текущему фильтру
|
// Находим целевые юниты, которые соответствуют ТЕКУЩЕМУ фильтру
|
||||||
var matchingTargetIds = await unitService.Get()
|
var matchingTargetIds = await unitService.Get()
|
||||||
.AsNoTracking()
|
.AsNoTracking()
|
||||||
.Where(u => allTargetIds.Contains(u.Id))
|
.Where(u => allTargetIds.Contains(u.Id))
|
||||||
.Where(u => u.UnitValues.Any(v =>
|
.Where(u => u.UnitValues.Any(v =>
|
||||||
v.FieldId == relFilter.FieldId &&
|
v.FieldId == relFilter.FieldId &&
|
||||||
EF.Functions.Like(v.Value.Value, dbValueMask)))
|
EF.Functions.ILike(v.Value.Value, dbValueMask)))
|
||||||
.Select(u => u.Id)
|
.Select(u => u.Id)
|
||||||
.ToListAsync(cancellationToken);
|
.ToListAsync(cancellationToken);
|
||||||
|
|
||||||
@@ -862,15 +826,36 @@ internal class UnitFilterService : IUnitFilterService
|
|||||||
isParentDirection ? "родителей" : "детей");
|
isParentDirection ? "родителей" : "детей");
|
||||||
|
|
||||||
// Считаем, сколько фильтров прошёл каждый TargetId
|
// Считаем, сколько фильтров прошёл каждый TargetId
|
||||||
foreach (var targetId in matchingTargetIds)
|
if (relFilter.IsInverse)
|
||||||
{
|
{
|
||||||
if (!targetFilterPassCount.ContainsKey(targetId))
|
// Для IsInverse: "проходит" фильтр тот, кто НЕ соответствует маске
|
||||||
targetFilterPassCount[targetId] = 0;
|
foreach (var targetId in allTargetIds)
|
||||||
targetFilterPassCount[targetId]++;
|
{
|
||||||
|
if (!matchingTargetIds.Contains(targetId))
|
||||||
|
{
|
||||||
|
if (!targetFilterPassCount.ContainsKey(targetId))
|
||||||
|
targetFilterPassCount[targetId] = 0;
|
||||||
|
targetFilterPassCount[targetId]++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.LogDebug(" Для IsInverse: {NonMatchCount} {TargetType} НЕ соответствуют маске",
|
||||||
|
allTargetIds.Count - matchingTargetIds.Count,
|
||||||
|
isParentDirection ? "родителей" : "детей");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Для обычного фильтра: "проходит" тот, кто соответствует маске
|
||||||
|
foreach (var targetId in matchingTargetIds)
|
||||||
|
{
|
||||||
|
if (!targetFilterPassCount.ContainsKey(targetId))
|
||||||
|
targetFilterPassCount[targetId] = 0;
|
||||||
|
targetFilterPassCount[targetId]++;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ✅ Оставляем только те связи, которые прошли ВСЕ фильтры
|
// Оставляем только те связи, которые прошли ВСЕ фильтры
|
||||||
int requiredFiltersCount = relFilters.Count(r => !string.IsNullOrWhiteSpace(r.ValueMask?.Trim()));
|
int requiredFiltersCount = relFilters.Count(r => !string.IsNullOrWhiteSpace(r.ValueMask?.Trim()));
|
||||||
var validTargetIds = targetFilterPassCount
|
var validTargetIds = targetFilterPassCount
|
||||||
.Where(kvp => kvp.Value == requiredFiltersCount)
|
.Where(kvp => kvp.Value == requiredFiltersCount)
|
||||||
@@ -898,22 +883,47 @@ internal class UnitFilterService : IUnitFilterService
|
|||||||
var validTargets = unitTargets.Intersect(validTargetIds).ToList();
|
var validTargets = unitTargets.Intersect(validTargetIds).ToList();
|
||||||
bool hasValidTargets = validTargets.Any();
|
bool hasValidTargets = validTargets.Any();
|
||||||
|
|
||||||
// Оцениваем, проходит ли юнит фильтр
|
// Логика с IsFullMatch
|
||||||
bool passesFilter = EvaluateFilter(
|
bool isFullMatch = relFilters.All(rf => rf.IsFullMatch);
|
||||||
validTargets, // Только связи, прошедшие ВСЕ фильтры
|
bool isInverse = relFilters.Any(rf => rf.IsInverse);
|
||||||
validTargets, // matchingRelations = validTargets (все валидные)
|
|
||||||
relFilters.All(rf => rf.IsFullMatch), // IsFullMatch для всех фильтров
|
|
||||||
relFilters.Any(rf => rf.IsInverse), // IsInverse для всех фильтров
|
|
||||||
hasTargets
|
|
||||||
);
|
|
||||||
|
|
||||||
if (passesFilter && hasValidTargets)
|
bool passesFilter;
|
||||||
|
|
||||||
|
if (!hasTargets)
|
||||||
{
|
{
|
||||||
// Сохраняем только валидные связи
|
// Нет связей вообще
|
||||||
if (isParentDirection)
|
passesFilter = isInverse; // IsInverse=true → проходит, иначе нет
|
||||||
context.ValidParentIds.UnionWith(validTargets);
|
}
|
||||||
|
else if (isFullMatch)
|
||||||
|
{
|
||||||
|
// ВСЕ связи должны быть валидными
|
||||||
|
passesFilter = validTargets.Count == unitTargets.Count;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// ХОТЯ БЫ ОДНА связь должна быть валидной
|
||||||
|
passesFilter = hasValidTargets;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (passesFilter)
|
||||||
|
{
|
||||||
|
// Сохраняем связи
|
||||||
|
if (isFullMatch)
|
||||||
|
{
|
||||||
|
// Для IsFullMatch=true: сохраняем ВСЕ связи (они все валидные)
|
||||||
|
if (isParentDirection)
|
||||||
|
context.ValidParentIds.UnionWith(unitTargets);
|
||||||
|
else
|
||||||
|
context.ValidChildIds.UnionWith(unitTargets);
|
||||||
|
}
|
||||||
else
|
else
|
||||||
context.ValidChildIds.UnionWith(validTargets);
|
{
|
||||||
|
// Для IsFullMatch=false: сохраняем ТОЛЬКО валидные связи
|
||||||
|
if (isParentDirection)
|
||||||
|
context.ValidParentIds.UnionWith(validTargets);
|
||||||
|
else
|
||||||
|
context.ValidChildIds.UnionWith(validTargets);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -951,7 +961,7 @@ internal class UnitFilterService : IUnitFilterService
|
|||||||
}
|
}
|
||||||
|
|
||||||
var initialUnitIds = await unitService.Get().AsNoTracking()
|
var initialUnitIds = await unitService.Get().AsNoTracking()
|
||||||
.Where(unit => EF.Functions.Like(unit.Name, filter.UnitFilter))
|
.Where(unit => EF.Functions.ILike(unit.Name, filter.UnitFilter))
|
||||||
.Select(u => u.Id)
|
.Select(u => u.Id)
|
||||||
.ToListAsync(cancellationToken);
|
.ToListAsync(cancellationToken);
|
||||||
|
|
||||||
|
|||||||
@@ -151,7 +151,7 @@ namespace PARR.DAL
|
|||||||
services.AddTransient<IUnitFilterService, UnitFilterService>();
|
services.AddTransient<IUnitFilterService, UnitFilterService>();
|
||||||
services.AddTransient<IMatchingStatusService, MatchingStatusService>();
|
services.AddTransient<IMatchingStatusService, MatchingStatusService>();
|
||||||
|
|
||||||
services.AddSingleton(new UnitFilterServiceOptions { LoadBatchSize = 1000 });
|
services.AddSingleton(new UnitFilterServiceOptions { LoadBatchSize = 50 });
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user