@@ -16,7 +16,7 @@ namespace PARR.Core.Services.UnitFilterService;
internal class UnitFilterService : IUnitFilterService
{
#if DEBUG
private readonly Guid t argetUnitId = Guid . Parse ( "9d88fff2-a861-487f-b73d-bce1f0218e9f " ) ;
private readonly Guid debugT argetUnitId = Guid . Parse ( "f2017292-193c-48e9-b333-3a00e737c6fc " ) ;
#endif
private const int DebugMaxUnitsToLog = 10 ;
@@ -94,7 +94,7 @@ internal class UnitFilterService : IUnitFilterService
logger . LogInformation ( "Начало фильтрации юнитов для Job {JobId} с {FilterCount} фильтрами" , job . Id , job . UnitFilters . Count ) ;
// Собираем все контексты юнитов, прошедших фильтрацию
var allFilteredContexts = new List < FilteredUnitContex t > ( ) ;
var allFilteredContexts = new List < Unit FilterMatchResul t> ( ) ;
// Преобразуем в список для индексации
var unitFiltersList = job . UnitFilters . ToList ( ) ;
@@ -118,9 +118,9 @@ internal class UnitFilterService : IUnitFilterService
}
#if DEBUG
if ( initialUnitIds . Contains ( t argetUnitId) )
if ( initialUnitIds . Contains ( debugT argetUnitId) )
{
logger . LogDebug ( "DEBUG: Юнит {TargetUnitId} найден в initialUnitIds для фильтра #{Index}" , t argetUnitId, i + 1 ) ;
logger . LogDebug ( "DEBUG: Юнит {TargetUnitId} найден в initialUnitIds для фильтра #{Index}" , debugT argetUnitId, i + 1 ) ;
}
#endif
@@ -136,13 +136,13 @@ internal class UnitFilterService : IUnitFilterService
}
#if DEBUG
if ( initialUnitIds . Contains ( t argetUnitId) & & ! fieldFilteredIds . Contains ( t argetUnitId) )
if ( initialUnitIds . Contains ( debugT argetUnitId) & & ! fieldFilteredIds . Contains ( debugT argetUnitId) )
{
logger . LogDebug ( "DEBUG: Юнит {TargetUnitId} ОТФИЛЬТРОВАН на этапе FieldFilters (Фильтр #{Index})" , t argetUnitId, i + 1 ) ;
logger . LogDebug ( "DEBUG: Юнит {TargetUnitId} ОТФИЛЬТРОВАН на этапе FieldFilters (Фильтр #{Index})" , debugT argetUnitId, i + 1 ) ;
}
#endif
// 3. Применить RelationshipFilters на уровне SQL -> ВОЗВРАЩАЕТ FilteredUnitContex t
// 3. Применить RelationshipFilters на уровне SQL -> ВОЗВРАЩАЕТ Unit FilterMatchResul t
var relStopwatch = Stopwatch . StartNew ( ) ;
var relationshipFilteredContexts = await ProcessRelationshipFiltersAsync ( fieldFilteredIds , filter . RelationshipFilters , cancellationToken ) ;
relStopwatch . Stop ( ) ;
@@ -154,11 +154,11 @@ internal class UnitFilterService : IUnitFilterService
}
#if DEBUG
var targetContext = relationshipFilteredContexts . FirstOrDefault ( c = > c . UnitId = = t argetUnitId) ;
var targetContext = relationshipFilteredContexts . FirstOrDefault ( c = > c . UnitId = = debugT argetUnitId) ;
if ( targetContext ! = null )
{
logger . LogDebug ( "DEBUG: Юнит {TargetUnitId} прошёл фильтр #{Index}. Родителей: {ParentCount}, Детей: {ChildCount}" ,
t argetUnitId, i + 1 , targetContext . ValidParentIds . Count , targetContext . ValidChildIds . Count ) ;
debugT argetUnitId, i + 1 , targetContext . ValidParentIds . Count , targetContext . ValidChildIds . Count ) ;
}
#endif
@@ -186,7 +186,7 @@ internal class UnitFilterService : IUnitFilterService
// Убираем дубликаты по UnitId, объединяя связи
var mergedContexts = allFilteredContexts
. GroupBy ( c = > c . UnitId )
. Select ( g = > new FilteredUnitContex t
. Select ( g = > new Unit FilterMatchResul t
{
UnitId = g . Key ,
ValidParentIds = new HashSet < Guid > ( g . SelectMany ( c = > c . ValidParentIds ) ) ,
@@ -204,7 +204,7 @@ internal class UnitFilterService : IUnitFilterService
job . MinValueRelationships , job . MaxValueRelationships , job . IsParentRelationships ) ;
#if DEBUG
var targetBeforeUmbrella = mergedContexts . FirstOrDefault ( c = > c . UnitId = = t argetUnitId) ;
var targetBeforeUmbrella = mergedContexts . FirstOrDefault ( c = > c . UnitId = = debugT argetUnitId) ;
if ( targetBeforeUmbrella ! = null )
{
var count = job . IsParentRelationships = = true
@@ -213,7 +213,7 @@ internal class UnitFilterService : IUnitFilterService
var passes = count > = ( job . MinValueRelationships ? ? 0 ) & & count < = ( job . MaxValueRelationships ? ? int . MaxValue ) ;
logger . LogDebug ( "DEBUG: Юнит {TargetUnitId} перед Umbrella: Count={Count}, Min={Min}, Max={Max}, Passes={Passes}" ,
t argetUnitId, count , job . MinValueRelationships , job . MaxValueRelationships , passes ) ;
debugT argetUnitId, count , job . MinValueRelationships , job . MaxValueRelationships , passes ) ;
}
#endif
@@ -222,10 +222,10 @@ internal class UnitFilterService : IUnitFilterService
logger . LogInformation ( "Этап фильтрации по числу связей завершён: после Umbrella-фильтра осталось {UnitCount} юнитов" , umbrellaFilteredContexts . Count ) ;
#if DEBUG
var targetAfterUmbrella = umbrellaFilteredContexts . FirstOrDefault ( c = > c . UnitId = = t argetUnitId) ;
var targetAfterUmbrella = umbrellaFilteredContexts . FirstOrDefault ( c = > c . UnitId = = debugT argetUnitId) ;
if ( targetBeforeUmbrella ! = null & & targetAfterUmbrella = = null )
{
logger . LogDebug ( "DEBUG: Юнит {TargetUnitId} ОТФИЛЬТРОВАН на этапе Umbrella" , t argetUnitId) ;
logger . LogDebug ( "DEBUG: Юнит {TargetUnitId} ОТФИЛЬТРОВАН на этапе Umbrella" , debugT argetUnitId) ;
}
#endif
}
@@ -249,7 +249,6 @@ internal class UnitFilterService : IUnitFilterService
return result ;
}
private async Task < Job ? > LoadJobWithFiltersAsync ( Guid jobId , CancellationToken cancellationToken = default )
{
return await jobService
@@ -294,39 +293,26 @@ internal class UnitFilterService : IUnitFilterService
logger . LogDebug ( " FieldFilter #{Index}: Поле='{FieldName}', Маска='{Mask}', IsInverse={IsInverse}" ,
filterIndex , fieldName , dbValueMask , fieldFilter . IsInverse ) ;
//if (fieldFilter.IsInverse)
//{
// query = query.Where(u => !u.UnitValues.Any(v =>
// v.FieldId == fieldFilter.FieldId &&
// EF.Functions.ILike(v.Value.Value, dbValueMask)));
//}
//else
//{
// query = query.Where(u => u.UnitValues.Any(v =>
// v.FieldId == fieldFilter.FieldId &&
// EF.Functions.ILike(v.Value.Value, dbValueMask)));
//}
query = unitService . GetUnitByFieldAndValue ( query , fieldFilter . FieldId , fieldFilter . ValueMask ! , fieldFilter . IsInverse ) ;
#if DEBUG
if ( unitIds . Contains ( t argetUnitId) )
if ( unitIds . Contains ( debugT argetUnitId) )
{
var unitHasField = await unitInValueService . Get ( )
. AsNoTracking ( )
. AnyAsync ( uiv = > uiv . UnitId = = t argetUnitId & & uiv . FieldId = = fieldFilter . FieldId , cancellationToken ) ;
. AnyAsync ( uiv = > uiv . UnitId = = debugT argetUnitId & & uiv . FieldId = = fieldFilter . FieldId , cancellationToken ) ;
var unitValue = await unitInValueService . Get ( )
. AsNoTracking ( )
. Where ( uiv = > uiv . UnitId = = t argetUnitId & & uiv . FieldId = = fieldFilter . FieldId )
. Where ( uiv = > uiv . UnitId = = debugT argetUnitId & & uiv . FieldId = = fieldFilter . FieldId )
. Select ( uiv = > uiv . Value . Value )
. FirstOrDefaultAsync ( cancellationToken ) ;
var intermediateResult = await query . Select ( u = > u . Id ) . ToListAsync ( cancellationToken ) ;
var passes = intermediateResult . Contains ( t argetUnitId) ;
var passes = intermediateResult . Contains ( debugT argetUnitId) ;
logger . LogDebug ( " DEBUG: Юнит {TargetUnitId}: Поле={FieldName}, Значение={Value}, Маска={Mask}, HasField={HasField}, Проходит={Passes}" ,
t argetUnitId, fieldName , unitValue ? ? "null" , dbValueMask , unitHasField , passes ) ;
debugT argetUnitId, fieldName , unitValue ? ? "null" , dbValueMask , unitHasField , passes ) ;
query = unitService . Get ( ) . AsNoTracking ( )
. Where ( u = > intermediateResult . Contains ( u . Id ) ) ;
@@ -344,7 +330,7 @@ internal class UnitFilterService : IUnitFilterService
/// <summary>
/// Применяем фильтры аттрибутов у связанных ЭК
/// </summary>
private async Task < List < FilteredUnitContex t > > ProcessRelationshipFiltersAsync (
private async Task < List < Unit FilterMatchResul t> > ProcessRelationshipFiltersAsync (
List < Guid > unitIds ,
IEnumerable < JobRelationshipFilter > relationshipFilters ,
CancellationToken cancellationToken = default )
@@ -353,29 +339,29 @@ internal class UnitFilterService : IUnitFilterService
{
logger . LogDebug ( "ProcessRelationshipFiltersAsync: вход {UnitCount} юнитов, фильтров: 0 → возврат без изменений" ,
unitIds . Count ) ;
return unitIds . Select ( id = > new FilteredUnitContex t { UnitId = id } ) . ToList ( ) ;
return unitIds . Select ( id = > new Unit FilterMatchResul t { UnitId = id } ) . ToList ( ) ;
}
logger . LogDebug ( "ProcessRelationshipFiltersAsync: вход {UnitCount} юнитов, фильтров: {FilterCount}" ,
unitIds . Count , relationshipFilters . Count ( ) ) ;
#if DEBUG
if ( unitIds . Contains ( t argetUnitId) )
if ( unitIds . Contains ( debugT argetUnitId) )
{
logger . LogDebug ( "DEBUG: Юнит {TargetUnitId} найден во входных данных ProcessRelationshipFiltersAsync" , t argetUnitId) ;
logger . LogDebug ( "DEBUG: Юнит {TargetUnitId} найден во входных данных ProcessRelationshipFiltersAsync" , debugT argetUnitId) ;
}
#endif
var resultContexts = unitIds . ToDictionary ( id = > id , id = > new FilteredUnitContex t { UnitId = id } ) ;
var p reFilteredUnitsById = unitIds . ToDictionary ( id = > id , id = > new Unit FilterMatchResul t { UnitId = id } ) ;
var parentRelFilters = relationshipFilters . Where ( rf = > rf . IsParent ) . ToList ( ) ;
var childRelFilters = relationshipFilters . Where ( rf = > ! rf . IsParent ) . ToList ( ) ;
if ( parentRelFilters . Any ( ) )
{
await ApplyRelationshipFiltersOnDb Async (
await ApplyRelationshipFiltersToUnits Async (
unitIds ,
resultContexts ,
p reFilteredUnitsById ,
parentRelFilters ,
isParentDirection : true ,
cancellationToken ) ;
@@ -383,48 +369,48 @@ internal class UnitFilterService : IUnitFilterService
if ( childRelFilters . Any ( ) )
{
await ApplyRelationshipFiltersOnDb Async (
await ApplyRelationshipFiltersToUnits Async (
unitIds ,
resultContexts ,
p reFilteredUnitsById ,
childRelFilters ,
isParentDirection : false ,
cancellationToken ) ;
}
#if DEBUG
var unitsWithAnyConnections = resultContexts . Values . Count ( c = > c . ValidParentIds . Any ( ) | | c . ValidChildIds . Any ( ) ) ;
var unitsWithoutConnections = resultContexts . Values . Count ( c = > ! c . ValidParentIds . Any ( ) & & ! c . ValidChildIds . Any ( ) ) ;
logger . LogDebug ( "DEBUG: После ApplyRelationshipFiltersOnDb Async: {WithConnections} юнитов с о связями, {WithoutConnections} без связей" ,
var unitsWithAnyConnections = p reFilteredUnitsById . Values . Count ( c = > c . ValidParentIds . Any ( ) | | c . ValidChildIds . Any ( ) ) ;
var unitsWithoutConnections = p reFilteredUnitsById . Values . Count ( c = > ! c . ValidParentIds . Any ( ) & & ! c . ValidChildIds . Any ( ) ) ;
logger . LogDebug ( "DEBUG: После ApplyRelationshipFiltersToUnits Async: {WithConnections} юнитов с о связями, {WithoutConnections} без связей" ,
unitsWithAnyConnections , unitsWithoutConnections ) ;
var targetContext = resultContexts . Values . FirstOrDefault ( c = > c . UnitId = = t argetUnitId) ;
var targetContext = p reFilteredUnitsById . Values . FirstOrDefault ( c = > c . UnitId = = debugT argetUnitId) ;
if ( targetContext ! = null )
{
logger . LogDebug ( "DEBUG: Юнит {TargetUnitId}: Родителей={ParentCount}, Детей={ChildCount}, PassesFilter={Passes}" ,
t argetUnitId,
debugT argetUnitId,
targetContext . ValidParentIds . Count ,
targetContext . ValidChildIds . Count ,
targetContext . ValidParentIds . Any ( ) | | targetContext . ValidChildIds . Any ( ) ) ;
}
#endif
var result = resultContexts . Values . ToList ( ) ;
var result = p reFilteredUnitsById . Values . ToList ( ) ;
logger . LogDebug ( "ProcessRelationshipFiltersAsync: выход {ContextCount} контекстов (из {InitialCount})" ,
result . Count , resultContexts . Count ) ;
result . Count , p reFilteredUnitsById . Count ) ;
#if DEBUG
if ( unitIds . Contains ( t argetUnitId) )
if ( unitIds . Contains ( debugT argetUnitId) )
{
var targetContextInResult = result . FirstOrDefault ( c = > c . UnitId = = t argetUnitId) ;
var targetContextInResult = result . FirstOrDefault ( c = > c . UnitId = = debugT argetUnitId) ;
if ( targetContextInResult ! = null )
{
logger . LogDebug ( "DEBUG: Юнит {TargetUnitId} в результатах ProcessRelationshipFiltersAsync. Родителей: {ParentCount}, Детей: {ChildCount}" ,
t argetUnitId, targetContextInResult . ValidParentIds . Count , targetContextInResult . ValidChildIds . Count ) ;
debugT argetUnitId, targetContextInResult . ValidParentIds . Count , targetContextInResult . ValidChildIds . Count ) ;
}
else
{
logger . LogDebug ( "DEBUG: Юнит {TargetUnitId} Н Е в результатах ProcessRelationshipFiltersAsync (удалён)" , t argetUnitId) ;
logger . LogDebug ( "DEBUG: Юнит {TargetUnitId} Н Е в результатах ProcessRelationshipFiltersAsync (удалён)" , debugT argetUnitId) ;
}
}
@@ -536,7 +522,7 @@ internal class UnitFilterService : IUnitFilterService
/// <param name="contexts"></param>
/// <param name="job"></param>
/// <returns></returns>
private List < FilteredUnitContex t > ApplyUmbrellaFilterInMemory ( List < FilteredUnitContex t > contexts , Job job )
private List < Unit FilterMatchResul t> ApplyUmbrellaFilterInMemory ( List < Unit FilterMatchResul t> contexts , Job job )
{
var min = job . MinValueRelationships ? ? 0 ;
var max = job . MaxValueRelationships ? ? int . MaxValue ;
@@ -545,17 +531,17 @@ internal class UnitFilterService : IUnitFilterService
if ( min = = 0 & & max = = int . MaxValue )
return contexts ;
var result = new List < FilteredUnitContex t > ( ) ;
var result = new List < Unit FilterMatchResul t> ( ) ;
foreach ( var context in contexts )
{
int count = isParentDirection ? context . ValidParentIds . Count : context . ValidChildIds . Count ;
bool passes = count > = min & & count < = max ;
#if DEBUG
if ( context . UnitId = = t argetUnitId)
if ( context . UnitId = = debugT argetUnitId)
{
logger . LogDebug ( "DEBUG Umbrella: Юнит {TargetUnitId}, Count={Count}, Min={Min}, Max={Max}, Passes={Passes}" ,
t argetUnitId, count , min , max , passes ) ;
debugT argetUnitId, count , min , max , passes ) ;
}
#endif
@@ -574,20 +560,20 @@ internal class UnitFilterService : IUnitFilterService
/// </summary>
/// <param name="contexts"></param>
/// <returns></returns>
private async Task < List < UnitFilterResultDto > > LoadFinalResultAsync ( List < FilteredUnitContex t > contexts , CancellationToken cancellationToken = default )
private async Task < List < UnitFilterResultDto > > LoadFinalResultAsync ( List < Unit FilterMatchResul t> contexts , CancellationToken cancellationToken = default )
{
var allResults = new List < UnitFilterResultDto > ( ) ;
#if DEBUG
var targetInContexts = contexts . FirstOrDefault ( c = > c . UnitId = = t argetUnitId) ;
var targetInContexts = contexts . FirstOrDefault ( c = > c . UnitId = = debugT argetUnitId) ;
if ( targetInContexts ! = null )
{
logger . LogDebug ( "DEBUG: Юнит {TargetUnitId} передан в LoadFinalResultAsync. Родителей: {ParentCount}, Детей: {ChildCount}" ,
t argetUnitId, targetInContexts . ValidParentIds . Count , targetInContexts . ValidChildIds . Count ) ;
debugT argetUnitId, targetInContexts . ValidParentIds . Count , targetInContexts . ValidChildIds . Count ) ;
}
else
{
logger . LogDebug ( "DEBUG: Юнит {TargetUnitId} Н Е передан в LoadFinalResultAsync" , t argetUnitId) ;
logger . LogDebug ( "DEBUG: Юнит {TargetUnitId} Н Е передан в LoadFinalResultAsync" , debugT argetUnitId) ;
}
#endif
@@ -607,7 +593,7 @@ internal class UnitFilterService : IUnitFilterService
/// </summary>
/// <param name="batch"></param>
/// <returns></returns>
private async Task < List < UnitFilterResultDto > > LoadBatchAsync ( List < FilteredUnitContex t > batch , CancellationToken cancellationToken = default )
private async Task < List < UnitFilterResultDto > > LoadBatchAsync ( List < Unit FilterMatchResul t> batch , CancellationToken cancellationToken = default )
{
var unitIds = batch . Select ( c = > c . UnitId ) . ToList ( ) ;
var allParentIds = batch . SelectMany ( c = > c . ValidParentIds ) . Distinct ( ) . ToList ( ) ;
@@ -786,12 +772,14 @@ internal class UnitFilterService : IUnitFilterService
#region в с п о м о г а т е л ь н ы е м е т о д ы
/// <summary>
/// Применяет фильтры к связям (родителям или детям)
/// </summary>
private async Task ApplyRelationshipFiltersOnDb Async (
private async Task ApplyRelationshipFiltersToUnits Async (
List < Guid > unitIds ,
Dictionary < Guid , FilteredUnitContex t > resultContexts ,
Dictionary < Guid , Unit FilterMatchResul t> p reFilteredUnitsById ,
List < JobRelationshipFilter > relFilters ,
bool isParentDirection ,
CancellationToken cancellationToken )
@@ -802,6 +790,7 @@ internal class UnitFilterService : IUnitFilterService
var directionName = isParentDirection ? "Родительские" : "Дочерние" ;
logger . LogDebug ( " {Direction} фильтры ({Count}):" , directionName , relFilters . Count ) ;
// 1. Получить все связи (unitInUnit) для юнитов из unitIds
var allLinks = await unitInUnitService . Get ( )
. AsNoTracking ( )
. Where ( link = > isParentDirection
@@ -814,16 +803,18 @@ internal class UnitFilterService : IUnitFilterService
} )
. ToListAsync ( cancellationToken ) ;
var allTarge tIds = allLinks . Select ( l = > l . TargetId ) . Distinct ( ) . ToList ( ) ;
var allRelatedUni tIds = allLinks . Select ( l = > l . TargetId ) . Distinct ( ) . ToList ( ) ;
logger . LogDebug ( " Найдено {TargetCount} уникальных {TargetType} для {LinkCount} связей" ,
allTarge tIds . Count ,
allRelatedUni tIds . Count ,
isParentDirection ? "родителей" : "детей" ,
allLinks . Count ) ;
var linksBySource = allLinks . GroupBy ( l = > l . SourceId )
// 2. Сгруппировать связи по SourceId (ID юнита из unitIds )
var relationshipsByUnitId = allLinks . GroupBy ( l = > l . SourceId )
. ToDictionary ( g = > g . Key , g = > g . Select ( l = > l . TargetId ) . ToList ( ) ) ;
var targetFilterPassCount = new Dictionary < Guid , int > ( ) ;
// 3. Для каждого фильтра в группе найдём TargetId, которые ему соответствуют (с учётом IsInverse)
var matchedTargetIdsByFilter = new Dictionary < JobRelationshipFilter , HashSet < Guid > > ( ) ;
foreach ( var relFilter in relFilters )
{
@@ -832,137 +823,156 @@ internal class UnitFilterService : IUnitFilterService
continue ;
string dbValueMask = NormalizeLikeMask ( 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 unitInValueService.Get()
// .AsNoTracking()
// .Where(uiv => uiv.FieldId == relFilter.FieldId)
// .Where(uiv => EF.Functions.ILike(uiv.Value.Value, dbValueMask))
// .Select(uiv => uiv.UnitId)
// .Distinct()
// .ToListAsync(cancellationToken);
var matchingTargetIds = await unitInValueService . GetMatchingTargetIds ( relFilter . FieldId , dbValueMask )
. ToListAsync ( cancellationToken ) ;
. ToListAsync ( cancellationToken ) ;
// Ограничиваем найденные ID только теми, которые действительно связаны с юнитами из unitIds
var filteredMatchingTargetIds = matchingTargetIds
. Intersect ( allTarge tIds )
. Intersect ( allRelatedUni tIds )
. ToList ( ) ;
logger . LogDebug ( " Найдено {MatchCount} {TargetType} по маске" ,
filteredMatchingTargetIds . Count ,
isParentDirection ? "родителей" : "детей" ) ;
HashSet < Guid > targetIdsThatPassThisFilter ;
if ( relFilter . IsInverse )
{
foreach ( var targetId in allTargetIds )
{
if ( ! filteredMatchingTargetIds . Contains ( t argetId) )
{
if ( ! targetFilterPassCount . ContainsKey ( targetId ) )
targetFilterPassCount [ targetId ] = 0 ;
targetFilterPassCount [ targetId ] + + ;
}
}
logger . LogDebug ( " Для IsInverse: {NonMatchCount} {TargetType} Н Е соответствуют маске" ,
allTargetIds . Count - filteredMatchingTargetIds . Count ,
isParentDirection ? "родителей" : "детей" ) ;
// Юнит проходит фильтр, если е г о значение Н Е соответствует маске
// Это означает, что юниты, Н Е входящие в filteredMatchingTargetIds, проходят фильтр
targetIdsThatPassThisFilter = new HashSet < Guid > ( allRelatedUnitIds . Except ( filteredMatchingT argetIds ) ) ;
logger . LogDebug ( " (IsInverse) Юниты, прошедшие фильтр: {Count}" , targetIdsThatPassThisFilter . Count ) ;
}
else
{
foreach ( var targetId in filteredMatchingTargetIds )
{
if ( ! targetFilterPassCount . ContainsKey ( targetId ) )
targetFilterPassCount [ targetId ] = 0 ;
targetFilterPassCount [ targetId ] + + ;
}
// Юнит проходит фильтр, если е г о значение соответствует маске
targetIdsThatPassThisFilter = new HashSet < Guid > ( filteredMatchingTargetIds ) ;
logger . LogDebug ( " (Direct) Юниты, прошедшие фильтр: {Count}" , targetIdsThatPassThisFilter . Cou nt ) ;
}
// Сохраняем результат для этого конкретного фильтра
matchedTargetIdsByFilter [ relFilter ] = targetIdsThatPassThisFilter ;
}
int requiredFiltersCount = relFilters . Count ( r = > ! string . IsN ullOrWhiteSpace ( r . ValueMask ? . Trim ( ) ) ) ;
var validTargetIds = targetFilterPassCount
. Where ( kvp = > kvp . Value = = requiredFiltersCount )
. Select ( kvp = > kvp . Key )
. ToList ( ) ;
// 4. Определим, какие юниты (context.UnitId) проходят В С Е фильтры с учётом IsF ullMatch
var unitIdsToRemove = new HashSet < Guid > ( ) ;
logger . LogDebug ( " Найдено {ValidCount} {TargetType}, прошедших В С Е {RequiredCount} фильтров" ,
validTargetIds . Count ,
isParentDirection ? "родителей" : "детей" ,
requiredFiltersCount ) ;
var unitsToRemove = new HashSet < Guid > ( ) ;
foreach ( var context in resultContexts . Values )
foreach ( var context in preFilteredUnitsById . Values )
{
if ( unitsToRemove . Contains ( context. UnitId) )
continue ;
var unitTargets = linksBySource . GetValueOrDefault ( context . UnitId , new List < Guid > ( ) ) ;
bool hasTargets = unitTargets . Any ( ) ;
var validTargets = unitTargets . Intersect ( validTargetIds ) . ToList ( ) ;
bool hasValidTargets = validTargets . Any ( ) ;
bool isFullMatch = relFilters . All ( rf = > rf . IsFullMatch ) ;
bool isInverse = relFilters . Any ( rf = > rf . IsInverse ) ;
bool passesFilter ;
// Получаем связанные юниты для конкретного context. UnitId
var relatedUnitIds = relationshipsByUnitId . GetValueOrDefault ( context . UnitId , new List < Guid > ( ) ) . ToHashSet ( ) ;
bool hasTargets = relatedUnitIds . Count > 0 ;
if ( ! hasTargets )
{
passesFilter = isInverse ;
// Нет связей - проверяем, есть ли фильтры, которые требуют наличия связей
// Если есть хотя бы один фильтр с маской, и связей нет - юнит не проходит.
var hasFiltersWithMask = relFilters . Any ( rf = > ! string . IsNullOrWhiteSpace ( rf . ValueMask ? . Trim ( ) ) ) ;
if ( hasFiltersWithMask )
{
unitIdsToRemove . Add ( context . UnitId ) ;
continue ; // Переходим к следующему юниту
}
// Если фильтров с маской нет, юнит остаётся.
}
else if ( isFullMatch )
bool unitPassesCurrentFilterGroup = true ;
// Проверяем, проходит ли юнит все фильтры в группе
foreach ( var relFilter in relFilters )
{
passesFilter = validTargets . Count = = unitTargets . Count ;
var valueMask = relFilter . ValueMask ? . Trim ( ) ;
if ( string . IsNullOrWhiteSpace ( valueMask ) )
{
continue ; // Пустой фильтр пропускаем
}
// Получаем юниты, прошедшие конкретный фильтр relFilter
var targetIdsThatPassThisFilter = matchedTargetIdsByFilter [ relFilter ] ;
if ( relFilter . IsFullMatch )
{
// В С Е relatedUnitIds должны пройти этот конкретный фильтр relFilter
// Это означает, что каждый relatedUnitId должен быть в targetIdsThatPassThisFilter
bool allTargetsPassThisFilter = relatedUnitIds . All ( targetId = > targetIdsThatPassThisFilter . Contains ( targetId ) ) ;
if ( ! allTargetsPassThisFilter )
{
unitPassesCurrentFilterGroup = false ;
break ; // Н е нужно проверять остальные фильтры, юнит заведомо не проходит
}
}
else // IsFullMatch = false
{
// Хоть один relatedUnitId должен пройти этот конкретный фильтр relFilter
// Это означает, что хотя бы один relatedUnitId должен быть в targetIdsThatPassThisFilter
bool anyTargetPassesThisFilter = relatedUnitIds . Any ( targetId = > targetIdsThatPassThisFilter . Contains ( targetId ) ) ;
if ( ! anyTargetPassesThisFilter )
{
unitPassesCurrentFilterGroup = false ;
break ; // Н е нужно проверять остальные фильтры, юнит заведимо не проходит
}
}
}
if ( ! unitPassesCurrentFilterGroup )
{
unitIdsToRemove . Add ( context . UnitId ) ;
}
else
{
passesFilter = hasValidTargets ;
}
// Юнит прошёл все фильтры.
// Теперь определим, какие из е г о связей (relatedUnitIds) идут в ValidParentIds/ValidChildIds.
// Это должны быть связи, прошедшие *все* фильтры (т.е . быть в пересечении результатов для каждого фильтра).
// Соберём пересечение всех targetId, прошедших каждый фильтр для этого конкретного юнита.
// Начинаем с множества всех е г о связей.
var validRelatedIds = new HashSet < Guid > ( relatedUnitIds ) ;
if ( passes Filter)
{
if ( isFullMatch )
foreach ( var relFilter in rel Filters )
{
if ( isParentDirection )
context . ValidParentIds . UnionWith ( unitTargets ) ;
else
context . ValidChildIds . UnionWith ( unitTargets ) ;
var valueMask = relFilter . ValueMask ? . Trim ( ) ;
if ( string . IsNullOrWhiteSpace ( valueMask ) )
continue ;
var targetIdsThatPassThisFilter = matchedTargetIdsByFilter [ relFilter ] ;
// Пересекаем текущий список validRelatedIds с результатами для этого фильтра
validRelatedIds . IntersectWith ( targetIdsThatPassThisFilter ) ;
}
// Добавляем полученные валидные связи в контекст юнита
if ( isParentDirection )
{
context . ValidParentIds . UnionWith ( validRelatedIds ) ;
}
else
{
if ( isParentDirection )
context . ValidParentIds . UnionWith ( validTargets ) ;
else
context . ValidChildIds . UnionWith ( validTargets ) ;
context . ValidChildIds . UnionWith ( validRelatedIds ) ;
}
}
else
{
unitsToRemove . Add ( context . UnitId ) ;
}
}
foreach ( var unitId in unitsToRemove )
// Удаляем юниты, которые не прошли проверку
foreach ( var unitId in unitIdsToRemove )
{
resultContexts . Remove ( unitId ) ;
p reFilteredUnitsById . Remove ( unitId ) ;
}
logger . LogDebug ( " Удалено {RemovedCount} юнитов, не прошедших фильтры" , unitsToRemove . Count ) ;
logger . LogDebug ( " Удалено {RemovedCount} юнитов, не прошедших фильтры (новая логика с IsFullMatch/IsInverse для каждого фильтра)" ,
unitIdsToRemove . Count ) ;
var totalAdded = resultContexts . Values . Sum ( c = >
var totalAdded = p reFilteredUnitsById . Values . Sum ( c = >
isParentDirection ? c . ValidParentIds . Count : c . ValidChildIds . Count ) ;
logger . LogDebug ( " Добавлено {Total} {TargetType} связей для {UnitCount} юнитов" ,
logger . LogDebug ( " Добавлено {Total} {TargetType} связей для {UnitCount} юнитов (новая логика) " ,
totalAdded ,
isParentDirection ? "родительских" : "дочерних" ,
resultContexts . Count ) ;
p reFilteredUnitsById . Count ) ;
}
@@ -979,11 +989,6 @@ internal class UnitFilterService : IUnitFilterService
var dbValueMask = NormalizeLikeMask ( filter . UnitFilter ) ;
//var initialUnitIds = await unitService.Get().AsNoTracking()
// .Where(unit => EF.Functions.ILike(unit.Name, dbValueMask))
// .Select(u => u.Id)
// .ToListAsync(cancellationToken);
var initialUnitIds = await unitService . GetInitialUnitIds ( dbValueMask ) . ToListAsync ( cancellationToken ) ;
var toCache = new UnitFilterIds