feat(dal): UnitFilterService добавлена обработка cancellationToken

This commit is contained in:
Mikhail Kuznetsov
2026-02-19 17:48:24 +10:00
parent d056f8b0c9
commit 6f692ba8e5
2 changed files with 60 additions and 40 deletions

View File

@@ -23,7 +23,8 @@ namespace PARR.DAL.DomainServices.UnitFilterService
/// <returns>Список ID Unit'ов</returns>
Task<IEnumerable<Guid>?> GetUnitsIdByJobFilterAsync(
Job job,
int? takeCount = null);
int? takeCount = null,
CancellationToken cancellationToken = default);
/// <summary>
/// Получает отфильтрованные Unit'ы с полной информацией о значениях полей и связях.
@@ -34,7 +35,8 @@ namespace PARR.DAL.DomainServices.UnitFilterService
/// <returns>Список UnitFilterResultDto или null, если Job не найден</returns>
Task<IEnumerable<UnitFilterResultDto>?> GetUnitsByJobFilterAsync(
Guid jobId,
int? takeCount = null);
int? takeCount = null,
CancellationToken cancellationToken = default);
/// <summary>
/// Получает отфильтрованные Unit'ы с полной информацией о значениях полей и связях.
@@ -45,7 +47,9 @@ namespace PARR.DAL.DomainServices.UnitFilterService
/// <returns>Список UnitFilterResultDto</returns>
Task<IEnumerable<UnitFilterResultDto>?> GetUnitsByJobFilterAsync(
Job job,
int? takeCount = null);
int? takeCount = null,
CancellationToken cancellationToken = default
);
/// <summary>
/// Получает имена связанных Unit'ов для заданного Unit, отфильтрованных по RelationshipFilters Job.
@@ -56,6 +60,8 @@ namespace PARR.DAL.DomainServices.UnitFilterService
/// <returns>Список имён связанных Unit'ов (уникальные, без дублей)</returns>
Task<List<string>> GetRelatedUnitNamesAsync(
Guid jobId,
Guid unitId);
Guid unitId,
CancellationToken cancellationToken = default
);
}
}

View File

@@ -49,21 +49,33 @@ internal class UnitFilterService : IUnitFilterService
}
public async Task<IEnumerable<UnitFilterResultDto>?> GetUnitsByJobFilterAsync(Guid jobId, int? takeCount = null)
public async Task<IEnumerable<UnitFilterResultDto>?> GetUnitsByJobFilterAsync(
Guid jobId,
int? takeCount = null,
CancellationToken cancellationToken = default
)
{
var job = await LoadJobWithFiltersAsync(jobId);
return job == null ? null : await GetUnitsByJobFilterAsync(job, takeCount);
return job == null ? null : await GetUnitsByJobFilterAsync(job, takeCount, cancellationToken);
}
public async Task<IEnumerable<Guid>?> GetUnitsIdByJobFilterAsync(Job job, int? takeCount = null)
public async Task<IEnumerable<Guid>?> GetUnitsIdByJobFilterAsync(
Job job,
int? takeCount = null,
CancellationToken cancellationToken = default
)
{
var filteredUnits = await GetUnitsByJobFilterAsync(job, takeCount);
var filteredUnits = await GetUnitsByJobFilterAsync(job, takeCount, cancellationToken);
return filteredUnits?.Select(u => u.Id).ToList();
}
public async Task<IEnumerable<UnitFilterResultDto>?> GetUnitsByJobFilterAsync(Job job, int? takeCount = null)
public async Task<IEnumerable<UnitFilterResultDto>?> GetUnitsByJobFilterAsync(
Job job,
int? takeCount = null,
CancellationToken cancellationToken = default
)
{
if (job.Group == null)
throw new ArgumentNullException(nameof(job.Group), $"Job {job.Id} не содержит Group");
@@ -92,7 +104,7 @@ internal class UnitFilterService : IUnitFilterService
logger.LogDebug("Применение фильтра #{Index} (Id={FilterId})", i + 1, filter.Id);
// 1. Найти ID юнитов по UnitFilter (Name LIKE)
var initialUnitIds = await GetUnitIdsFromCacheOrDbAsync(filter);
var initialUnitIds = await GetUnitIdsFromCacheOrDbAsync(filter, cancellationToken);
if (!initialUnitIds.Any())
{
logger.LogDebug("Фильтр #{Index}: пропущен (0 юнитов)", i + 1);
@@ -102,13 +114,13 @@ internal class UnitFilterService : IUnitFilterService
#if DEBUG
if (initialUnitIds.Contains(targetUnitId))
{
logger.LogDebug("🎯 DEBUG: Юнит {TargetUnitId} найден в initialUnitIds для фильтра #{Index}", targetUnitId, i + 1);
logger.LogDebug("DEBUG: Юнит {TargetUnitId} найден в initialUnitIds для фильтра #{Index}", targetUnitId, i + 1);
}
#endif
// 2. Применить FieldFilters на уровне SQL
var fieldStopwatch = Stopwatch.StartNew();
var fieldFilteredIds = await ApplyFieldFiltersOnDbAsync(initialUnitIds, filter.FieldFilters);
var fieldFilteredIds = await ApplyFieldFiltersOnDbAsync(initialUnitIds, filter.FieldFilters, cancellationToken);
fieldStopwatch.Stop();
if (!fieldFilteredIds.Any())
@@ -120,13 +132,13 @@ internal class UnitFilterService : IUnitFilterService
#if DEBUG
if (initialUnitIds.Contains(targetUnitId) && !fieldFilteredIds.Contains(targetUnitId))
{
logger.LogDebug("🎯 DEBUG: Юнит {TargetUnitId} ОТФИЛЬТРОВАН на этапе FieldFilters (Фильтр #{Index})", targetUnitId, i + 1);
logger.LogDebug("DEBUG: Юнит {TargetUnitId} ОТФИЛЬТРОВАН на этапе FieldFilters (Фильтр #{Index})", targetUnitId, i + 1);
}
#endif
// 3. Применить RelationshipFilters на уровне SQL -> ВОЗВРАЩАЕТ FilteredUnitContext
var relStopwatch = Stopwatch.StartNew();
var relationshipFilteredContexts = await ApplyRelationshipFiltersOnDbAsync(fieldFilteredIds, filter.RelationshipFilters);
var relationshipFilteredContexts = await ApplyRelationshipFiltersOnDbAsync(fieldFilteredIds, filter.RelationshipFilters, cancellationToken);
relStopwatch.Stop();
if (!relationshipFilteredContexts.Any())
@@ -139,7 +151,7 @@ internal class UnitFilterService : IUnitFilterService
var targetContext = relationshipFilteredContexts.FirstOrDefault(c => c.UnitId == targetUnitId);
if (targetContext != null)
{
logger.LogDebug("🎯 DEBUG: Юнит {TargetUnitId} прошёл фильтр #{Index}. Родителей: {ParentCount}, Детей: {ChildCount}",
logger.LogDebug("DEBUG: Юнит {TargetUnitId} прошёл фильтр #{Index}. Родителей: {ParentCount}, Детей: {ChildCount}",
targetUnitId, i + 1, targetContext.ValidParentIds.Count, targetContext.ValidChildIds.Count);
}
#endif
@@ -194,7 +206,7 @@ internal class UnitFilterService : IUnitFilterService
: targetBeforeUmbrella.ValidChildIds.Count;
var passes = count >= (job.MinValueRelationships ?? 0) && count <= (job.MaxValueRelationships ?? int.MaxValue);
logger.LogDebug("🎯 DEBUG: Юнит {TargetUnitId} перед Umbrella: Count={Count}, Min={Min}, Max={Max}, Passes={Passes}",
logger.LogDebug("DEBUG: Юнит {TargetUnitId} перед Umbrella: Count={Count}, Min={Min}, Max={Max}, Passes={Passes}",
targetUnitId, count, job.MinValueRelationships, job.MaxValueRelationships, passes);
}
#endif
@@ -207,7 +219,7 @@ internal class UnitFilterService : IUnitFilterService
var targetAfterUmbrella = umbrellaFilteredContexts.FirstOrDefault(c => c.UnitId == targetUnitId);
if (targetBeforeUmbrella != null && targetAfterUmbrella == null)
{
logger.LogDebug("🎯 DEBUG: Юнит {TargetUnitId} ОТФИЛЬТРОВАН на этапе Umbrella", targetUnitId);
logger.LogDebug("DEBUG: Юнит {TargetUnitId} ОТФИЛЬТРОВАН на этапе Umbrella", targetUnitId);
}
#endif
}
@@ -252,7 +264,8 @@ internal class UnitFilterService : IUnitFilterService
/// <returns></returns>
private async Task<List<Guid>> ApplyFieldFiltersOnDbAsync(
List<Guid> unitIds,
IEnumerable<JobFieldFilter> fieldFilters)
IEnumerable<JobFieldFilter> fieldFilters,
CancellationToken cancellationToken = default)
{
if (!fieldFilters.Any())
return unitIds;
@@ -303,7 +316,7 @@ internal class UnitFilterService : IUnitFilterService
}
// Выполняем промежуточный запрос для логирования
var intermediateResult = await query.Select(u => u.Id).ToListAsync();
var intermediateResult = await query.Select(u => u.Id).ToListAsync(cancellationToken);
logger.LogDebug(" После фильтра #{Index}: осталось {Count} юнитов", filterIndex, intermediateResult.Count);
// Обновляем query для следующей итерации
@@ -311,7 +324,7 @@ internal class UnitFilterService : IUnitFilterService
.Where(u => intermediateResult.Contains(u.Id));
}
var result = await query.Select(u => u.Id).ToListAsync();
var result = await query.Select(u => u.Id).ToListAsync(cancellationToken);
logger.LogDebug("ApplyFieldFiltersOnDbAsync: выход {UnitCount} юнитов", result.Count);
return result;
@@ -326,7 +339,8 @@ internal class UnitFilterService : IUnitFilterService
/// <returns></returns>
private async Task<List<FilteredUnitContext>> ApplyRelationshipFiltersOnDbAsync(
List<Guid> unitIds,
IEnumerable<JobRelationshipFilter> relationshipFilters
IEnumerable<JobRelationshipFilter> relationshipFilters,
CancellationToken cancellationToken = default
)
{
if (!relationshipFilters.Any() || !unitIds.Any())
@@ -342,7 +356,7 @@ internal class UnitFilterService : IUnitFilterService
#if DEBUG
if (unitIds.Contains(targetUnitId))
{
logger.LogDebug("🎯 DEBUG: Юнит {TargetUnitId} присутствует во входных данных ApplyRelationshipFiltersOnDbAsync", targetUnitId);
logger.LogDebug("DEBUG: Юнит {TargetUnitId} присутствует во входных данных ApplyRelationshipFiltersOnDbAsync", targetUnitId);
}
#endif
@@ -360,7 +374,7 @@ internal class UnitFilterService : IUnitFilterService
.AsNoTracking()
.Where(link => unitIds.Contains(link.ChildUnitId))
.Select(link => new { ChildId = link.ChildUnitId, ParentId = link.ParentUnitId })
.ToListAsync();
.ToListAsync(cancellationToken);
var allParentIds = allParentLinks.Select(l => l.ParentId).Distinct().ToList();
logger.LogDebug(" Найдено {ParentCount} уникальных родителей для {LinkCount} связей",
@@ -398,7 +412,7 @@ internal class UnitFilterService : IUnitFilterService
v.FieldId == relFilter.FieldId &&
EF.Functions.Like(v.Value.Value, dbValueMask)))
.Select(u => u.Id)
.ToListAsync();
.ToListAsync(cancellationToken);
logger.LogDebug(" Найдено {MatchCount} родителей по маске", matchingParents.Count);
@@ -439,7 +453,7 @@ internal class UnitFilterService : IUnitFilterService
.AsNoTracking()
.Where(link => unitIds.Contains(link.ParentUnitId))
.Select(link => new { ParentId = link.ParentUnitId, ChildId = link.ChildUnitId })
.ToListAsync();
.ToListAsync(cancellationToken);
var allChildIds = allChildLinks.Select(l => l.ChildId).Distinct().ToList();
logger.LogDebug(" Найдено {ChildCount} уникальных детей для {LinkCount} связей",
@@ -477,7 +491,7 @@ internal class UnitFilterService : IUnitFilterService
v.FieldId == relFilter.FieldId &&
EF.Functions.Like(v.Value.Value, dbValueMask)))
.Select(u => u.Id)
.ToListAsync();
.ToListAsync(cancellationToken);
logger.LogDebug(" Найдено {MatchCount} детей по маске", matchingChildren.Count);
@@ -540,7 +554,7 @@ internal class UnitFilterService : IUnitFilterService
#if DEBUG
if (context.UnitId == targetUnitId)
{
logger.LogDebug("🎯 DEBUG Umbrella: Юнит {TargetUnitId}, Count={Count}, Min={Min}, Max={Max}, Passes={Passes}",
logger.LogDebug("DEBUG Umbrella: Юнит {TargetUnitId}, Count={Count}, Min={Min}, Max={Max}, Passes={Passes}",
targetUnitId, count, min, max, passes);
}
#endif
@@ -559,7 +573,7 @@ internal class UnitFilterService : IUnitFilterService
/// </summary>
/// <param name="contexts"></param>
/// <returns></returns>
private async Task<List<UnitFilterResultDto>> LoadFinalResultAsync(List<FilteredUnitContext> contexts)
private async Task<List<UnitFilterResultDto>> LoadFinalResultAsync(List<FilteredUnitContext> contexts, CancellationToken cancellationToken = default)
{
var allResults = new List<UnitFilterResultDto>();
@@ -567,19 +581,19 @@ internal class UnitFilterService : IUnitFilterService
var targetInContexts = contexts.FirstOrDefault(c => c.UnitId == targetUnitId);
if (targetInContexts != null)
{
logger.LogDebug("🎯 DEBUG: Юнит {TargetUnitId} передан в LoadFinalResultAsync. Родителей: {ParentCount}, Детей: {ChildCount}",
logger.LogDebug("DEBUG: Юнит {TargetUnitId} передан в LoadFinalResultAsync. Родителей: {ParentCount}, Детей: {ChildCount}",
targetUnitId, targetInContexts.ValidParentIds.Count, targetInContexts.ValidChildIds.Count);
}
else
{
logger.LogDebug("🎯 DEBUG: Юнит {TargetUnitId} НЕ передан в LoadFinalResultAsync", targetUnitId);
logger.LogDebug("DEBUG: Юнит {TargetUnitId} НЕ передан в LoadFinalResultAsync", targetUnitId);
}
#endif
for (int i = 0; i < contexts.Count; i += batchSize)
{
var batch = contexts.Skip(i).Take(batchSize).ToList();
var batchResults = await LoadBatchAsync(batch);
var batchResults = await LoadBatchAsync(batch, cancellationToken);
allResults.AddRange(batchResults);
}
@@ -592,7 +606,7 @@ internal class UnitFilterService : IUnitFilterService
/// </summary>
/// <param name="batch"></param>
/// <returns></returns>
private async Task<List<UnitFilterResultDto>> LoadBatchAsync(List<FilteredUnitContext> batch)
private async Task<List<UnitFilterResultDto>> LoadBatchAsync(List<FilteredUnitContext> batch, CancellationToken cancellationToken = default)
{
var unitIds = batch.Select(c => c.UnitId).ToList();
var allParentIds = batch.SelectMany(c => c.ValidParentIds).Distinct().ToList();
@@ -603,7 +617,7 @@ internal class UnitFilterService : IUnitFilterService
.AsSingleQuery()
.Include(u => u.UnitValues).ThenInclude(v => v.Value)
.Where(u => unitIds.Contains(u.Id))
.ToDictionaryAsync(u => u.Id);
.ToDictionaryAsync(u => u.Id, cancellationToken);
// Загружаем родителей
Dictionary<Guid, Unit> parentsMap;
@@ -613,7 +627,7 @@ internal class UnitFilterService : IUnitFilterService
.AsSingleQuery()
.Include(u => u.UnitValues).ThenInclude(v => v.Value)
.Where(u => allParentIds.Contains(u.Id))
.ToDictionaryAsync(u => u.Id);
.ToDictionaryAsync(u => u.Id, cancellationToken);
}
else
{
@@ -628,7 +642,7 @@ internal class UnitFilterService : IUnitFilterService
.AsSingleQuery()
.Include(u => u.UnitValues).ThenInclude(v => v.Value)
.Where(u => allChildIds.Contains(u.Id))
.ToDictionaryAsync(u => u.Id);
.ToDictionaryAsync(u => u.Id, cancellationToken);
}
else
{
@@ -681,14 +695,14 @@ internal class UnitFilterService : IUnitFilterService
public async Task<List<string>> GetRelatedUnitNamesAsync(Guid jobId, Guid unitId)
public async Task<List<string>> GetRelatedUnitNamesAsync(Guid jobId, Guid unitId, CancellationToken cancellationToken = default)
{
logger.LogDebug("Начало GetRelatedUnitNamesAsync. JobId: {JobId}, UnitId: {UnitId}", jobId, unitId);
var job = await jobService
.Get().AsNoTracking()
.Include(j => j.UnitFilters).ThenInclude(uf => uf.RelationshipFilters)
.FirstOrDefaultAsync(j => j.Id == jobId);
.FirstOrDefaultAsync(j => j.Id == jobId, cancellationToken);
if (job == null)
{
@@ -762,7 +776,7 @@ internal class UnitFilterService : IUnitFilterService
var names = await unitService.Get().AsNoTracking()
.Where(u => matchingUnitIds.Contains(u.Id))
.Select(u => u.Name)
.ToListAsync();
.ToListAsync(cancellationToken);
result.UnionWith(names);
}
}
@@ -772,7 +786,7 @@ internal class UnitFilterService : IUnitFilterService
#region вспомогательные методы
private async Task<List<Guid>> GetUnitIdsFromCacheOrDbAsync(JobUnitFilter filter)
private async Task<List<Guid>> GetUnitIdsFromCacheOrDbAsync(JobUnitFilter filter, CancellationToken cancellationToken = default)
{
var cacheKey = cacheService.GetKey(new[] { "uf_ids", filter.UnitFilter }, isUseHash: true);
@@ -785,7 +799,7 @@ internal class UnitFilterService : IUnitFilterService
var initialUnitIds = await unitService.Get().AsNoTracking()
.Where(unit => EF.Functions.Like(unit.Name, filter.UnitFilter))
.Select(u => u.Id)
.ToListAsync();
.ToListAsync(cancellationToken);
var toCache = new UnitFilterIds
{