diff --git a/PARR.API/Contracts/V1/Requests/Queries/StatWorkloadTemplateReqportQuery.cs b/PARR.API/Contracts/V1/Requests/Queries/StatWorkloadTemplateReqportQuery.cs
index 53273ac1..8a7c9d17 100644
--- a/PARR.API/Contracts/V1/Requests/Queries/StatWorkloadTemplateReqportQuery.cs
+++ b/PARR.API/Contracts/V1/Requests/Queries/StatWorkloadTemplateReqportQuery.cs
@@ -1,17 +1,15 @@
-using PARR.Domain.Enums.Workload;
-
-namespace PARR.API.Contracts.V1.Requests.Queries
+namespace PARR.API.Contracts.V1.Requests.Queries
{
public record StatWorkloadTemplateReqportQuery
{
///
- /// Тип дополнительно фильтра для работы (ЗО, РГ)
+ /// Дополнительный фильтр для работ - ЗО
///
- public WorkloadJobSubfliterType? JobSubfilterType { get; init; }
+ public string? ResponseArea { get; init; }
///
- /// Значение дополнительного фильтра для работы
+ /// Дополнительный фильтр для работ - РГ
///
- public string? JobSubfilterValue { get; init; }
+ public string? WorkGroup { get; init; }
}
}
diff --git a/PARR.API/Controllers/V1/Statistics/StatWorkloadController.cs b/PARR.API/Controllers/V1/Statistics/StatWorkloadController.cs
index ed47af74..0a8dade1 100644
--- a/PARR.API/Controllers/V1/Statistics/StatWorkloadController.cs
+++ b/PARR.API/Controllers/V1/Statistics/StatWorkloadController.cs
@@ -57,7 +57,7 @@ namespace PARR.API.Controllers.V1.Statistics
[HttpGet(ApiRoutes.Workload.WorkGroup)]
public async Task GetWorkGroupReport([FromQuery] TimeZoneOffsetClient offset, [FromQuery] WorkloadBaseQuery request)
{
- return await GetReportAsync(WorkloadReportType.WorkGroupAll, request.DurationDays, offset.TimeZoneOffset, null);
+ return await GetReportAsync(WorkloadReportType.WorkGroupAll, request.DurationDays, offset.TimeZoneOffset, null, null);
}
@@ -69,12 +69,12 @@ namespace PARR.API.Controllers.V1.Statistics
///
///
[HttpGet(ApiRoutes.Workload.WorkGroupJob)]
- public async Task GetWorkGroupJobReport([FromRoute] string workGroup, [FromQuery] TimeZoneOffsetClient offset, [FromQuery] WorkloadBaseQuery request)
+ public async Task GetWorkGroupJobReport([FromRoute] string workGroup, [FromQuery] string? responseArea, [FromQuery] TimeZoneOffsetClient offset, [FromQuery] WorkloadBaseQuery request)
{
if (string.IsNullOrWhiteSpace(workGroup))
throw new AppValidationException("Не указано значение рабочей группы");
- return await GetReportAsync(WorkloadReportType.WorkGroupJob, request.DurationDays, offset.TimeZoneOffset, workGroup);
+ return await GetReportAsync(WorkloadReportType.WorkGroupJob, request.DurationDays, offset.TimeZoneOffset, responseArea, workGroup);
}
@@ -85,7 +85,7 @@ namespace PARR.API.Controllers.V1.Statistics
[HttpGet(ApiRoutes.Workload.ResponseArea)]
public async Task GetResponseAreaReport([FromQuery] TimeZoneOffsetClient offset, [FromQuery] WorkloadBaseQuery request)
{
- return await GetReportAsync(WorkloadReportType.ResponseAreaAll, request.DurationDays, offset.TimeZoneOffset, null);
+ return await GetReportAsync(WorkloadReportType.ResponseAreaAll, request.DurationDays, offset.TimeZoneOffset, null, null);
}
@@ -102,7 +102,7 @@ namespace PARR.API.Controllers.V1.Statistics
if (string.IsNullOrWhiteSpace(responseArea))
throw new AppValidationException("Не указано значение зоны ответственности");
- return await GetReportAsync(WorkloadReportType.ResponseAreaWorkGroups, request.DurationDays, offset.TimeZoneOffset, responseArea);
+ return await GetReportAsync(WorkloadReportType.ResponseAreaWorkGroups, request.DurationDays, offset.TimeZoneOffset, responseArea, null);
}
@@ -147,13 +147,14 @@ namespace PARR.API.Controllers.V1.Statistics
/// Универсальный метод получения отчетности
///
///
- private async Task GetReportAsync(WorkloadReportType reportType, int durationDays, TimeSpan offset, string? filter)
+ //private async Task GetReportAsync(WorkloadReportType reportType, int durationDays, TimeSpan offset, string? filter)
+ private async Task GetReportAsync(WorkloadReportType reportType, int durationDays, TimeSpan offset, string? responseArea, string? workGroup)
{
var utcNow = DateTimeOffset.UtcNow;
var localTime = utcNow.ToOffset(offset);
var dateStart = DateOnly.FromDateTime(localTime.Date);
- var report = await workloadService.GetWorkloadReportAsync(reportType, dateStart, durationDays, offset, filter?.Trim());
+ var report = await workloadService.GetWorkloadReportAsync(reportType, dateStart, durationDays, offset, responseArea?.Trim(), workGroup?.Trim());
if (report == null)
return BadRequest(new Response(null, false, new List { new ErrorModel { Message = "Необходимо сформировать отчетность." } }, "empty"));
diff --git a/PARR.API/Controllers/V1/Statistics/StatWorkloadTemplateController.cs b/PARR.API/Controllers/V1/Statistics/StatWorkloadTemplateController.cs
index b443fa5e..7ae670fa 100644
--- a/PARR.API/Controllers/V1/Statistics/StatWorkloadTemplateController.cs
+++ b/PARR.API/Controllers/V1/Statistics/StatWorkloadTemplateController.cs
@@ -38,11 +38,11 @@ namespace PARR.API.Controllers.V1.Statistics
///
/// Отчет о загруженности. Шаблоны
///
- ///
- ///
- ///
- ///
- ///
+ /// Тип отчета (ЗО, РГ, Работа)
+ /// Фильтр, значиение в зависимости от типа отчета
+ /// Все, активированные, деактивированные
+ /// Дата
+ /// Часовой пояс
///
///
[HttpGet(ApiRoutes.StatWorkloadTemplate.GetWorkloadTemplateReport)]
@@ -61,13 +61,7 @@ namespace PARR.API.Controllers.V1.Statistics
throw new ArgumentException(nameof(filter), "Не передано значение фильтра.");
}
- if (query.JobSubfilterType.HasValue && string.IsNullOrWhiteSpace(query.JobSubfilterValue))
- {
- logger.LogWarning("Не передано значение дополнительного фильтра для работы");
- throw new ArgumentException(nameof(filter), "Не передано значение дополнительного фильтра для работы.");
- }
-
- var report = await workloadService.GetTemplateReportAsync(reportType, filter, state, date, offset.TimeZoneOffset, query.JobSubfilterType, query.JobSubfilterValue);
+ var report = await workloadService.GetTemplateReportAsync(reportType, filter.Trim(), state, date, offset.TimeZoneOffset, query.ResponseArea?.Trim(), query.WorkGroup?.Trim());
var response = mapper.Map>(report);
diff --git a/PARR.Core/Services/Workload/Implementations/WorkloadCacheService.cs b/PARR.Core/Services/Workload/Implementations/WorkloadCacheService.cs
index 812d89db..a4d8cc85 100644
--- a/PARR.Core/Services/Workload/Implementations/WorkloadCacheService.cs
+++ b/PARR.Core/Services/Workload/Implementations/WorkloadCacheService.cs
@@ -415,9 +415,9 @@ namespace PARR.Core.Services.Workload.Implementations
///
/// Наименование ЗО или РГ или null
///
- public async Task GetWorkloadReportAsync(WorkloadReportType reportType, DateOnly dateStart, int durationDays, TimeSpan offset, string? filterParam)
+ public async Task GetWorkloadReportAsync(WorkloadReportType reportType, DateOnly dateStart, int durationDays, TimeSpan offset, string? responseArea, string? workGroup)
{
- var key = GetReportCacheKey(reportType, dateStart, durationDays, offset, filterParam);
+ var key = GetReportCacheKey(reportType, dateStart, durationDays, offset, responseArea, workGroup);
return await redisCacheService.GetCachedDataAsync(key, true);
}
@@ -433,9 +433,9 @@ namespace PARR.Core.Services.Workload.Implementations
///
/// Наименование ЗО или РГ или null
///
- public async Task SetWorkloadReport(WorkloadReport report, WorkloadReportType reportType, DateOnly dateStart, int durationDays, TimeSpan offset, string? filterParam)
+ public async Task SetWorkloadReport(WorkloadReport report, WorkloadReportType reportType, DateOnly dateStart, int durationDays, TimeSpan offset, string? responseArea, string? workGroup)
{
- var key = GetReportCacheKey(reportType, dateStart, durationDays, offset, filterParam);
+ var key = GetReportCacheKey(reportType, dateStart, durationDays, offset, responseArea, workGroup);
await redisCacheService.SetCachedDataAsync(key, report, cacheWorkloadTtl, true);
}
@@ -462,10 +462,10 @@ namespace PARR.Core.Services.Workload.Implementations
///
///
///
- private string GetReportCacheKey(WorkloadReportType reportType, DateOnly dateStart, int durationDays, TimeSpan offset, string? filterParam = null)
+ private string GetReportCacheKey(WorkloadReportType reportType, DateOnly dateStart, int durationDays, TimeSpan offset, string? responseArea, string? workGroup)
{
// формируе ключ, вида: основа + дата начала + продолжительность + смещение
- // Если есть параметр (filterParam), добавляется параметр в виде Hash
+ // Если есть параметр (responseArea, workGroup), добавляется параметр в виде Hash
var keyList = new List();
@@ -493,10 +493,10 @@ namespace PARR.Core.Services.Workload.Implementations
var keyStr = string.Empty;
- if (filterParam == null)
+ if (responseArea == null && workGroup == null)
keyStr = redisCacheService.GetKey(keyList.ToArray());
else
- keyStr = redisCacheService.GetKey(keyList.ToArray(), new string[] { filterParam });
+ keyStr = redisCacheService.GetKey(keyList.ToArray(), new string[] { responseArea ?? string.Empty, workGroup ?? string.Empty });
logger.LogDebug("Сформирован ключ для кэш '{Key}'", keyStr);
diff --git a/PARR.Core/Services/Workload/Implementations/WorkloadService.cs b/PARR.Core/Services/Workload/Implementations/WorkloadService.cs
index 07f98fe3..c1531fa7 100644
--- a/PARR.Core/Services/Workload/Implementations/WorkloadService.cs
+++ b/PARR.Core/Services/Workload/Implementations/WorkloadService.cs
@@ -8,6 +8,7 @@ using PARR.Core.Services.NextRunServices;
using PARR.Core.Services.TaskServices.Helpers;
using PARR.Core.Services.Workload.Interfaces;
using PARR.Core.Services.Workload.Models;
+using PARR.Domain.Cache.Models;
using PARR.Domain.DTOs.Workload;
using PARR.Domain.Entities;
using PARR.Domain.Enums;
@@ -45,19 +46,25 @@ namespace PARR.Core.Services.Workload.Implementations
}
- public async Task GetWorkloadReportAsync(WorkloadReportType reportType, DateOnly dateStart, int durationDays, TimeSpan offset, string? filterParam)
+ public async Task GetWorkloadReportAsync(WorkloadReportType reportType, DateOnly dateStart, int durationDays, TimeSpan offset, string? responseArea, string? workGroup)
{
logger.LogDebug("Начинаю формировать отчет о загруженности по параметрам: reportType '{ReportType}', dateStart {DateStart}, durationDays {DurationDays}, offset {Offset}",
reportType.ToString(), dateStart, durationDays, offset);
- if ((reportType == WorkloadReportType.WorkGroupJob || reportType == WorkloadReportType.ResponseAreaWorkGroups) && string.IsNullOrEmpty(filterParam))
+ if (reportType == WorkloadReportType.WorkGroupJob && string.IsNullOrEmpty(workGroup))
{
logger.LogError("Для типа отчета {ReportType} не передан параметр.", reportType.ToString());
- throw new ArgumentNullException(nameof(filterParam), $"Для отчета типа {reportType.ToString()} параметр является обязательным.");
+ throw new ArgumentNullException(nameof(workGroup), $"Для отчета типа {reportType.ToString()} параметр является обязательным.");
+ }
+
+ if (reportType == WorkloadReportType.ResponseAreaWorkGroups && string.IsNullOrEmpty(responseArea))
+ {
+ logger.LogError("Для типа отчета {ReportType} не передан параметр.", reportType.ToString());
+ throw new ArgumentNullException(nameof(responseArea), $"Для отчета типа {reportType.ToString()} параметр является обязательным.");
}
// Если есть отчет в КЭШ, вернуть
- var cacheReport = await workloadCacheService.GetWorkloadReportAsync(reportType, dateStart, durationDays, offset, filterParam);
+ var cacheReport = await workloadCacheService.GetWorkloadReportAsync(reportType, dateStart, durationDays, offset, responseArea, workGroup);
if (cacheReport != null)
{
logger.LogDebug("Вернул отчет из кэша.");
@@ -92,11 +99,10 @@ namespace PARR.Core.Services.Workload.Implementations
case WorkloadReportType.ResponseAreaWorkGroups:
displayName = "По зоне ответственности";
// Фильтруем по ЗО
- var filteredRA = templates.Where(t => string.Equals(t.ResponseArea, filterParam, StringComparison.OrdinalIgnoreCase)).ToList();
+ var filteredRA = templates.Where(t => string.Equals(t.ResponseArea, responseArea, StringComparison.OrdinalIgnoreCase)).ToList();
// Группируем работы по РГ
foreach (var group in filteredRA.GroupBy(t => t.WorkGroup, StringComparer.OrdinalIgnoreCase))
statistics.Add(BuildStatisticItem(group.Key, null, group, reportDays, offset));
- //statistics.Add(BuildStatisticItem(filterParam!, filteredRA, reportDays, offset));
break;
case WorkloadReportType.WorkGroupAll:
displayName = "По рабочим группам";
@@ -106,7 +112,12 @@ namespace PARR.Core.Services.Workload.Implementations
case WorkloadReportType.WorkGroupJob:
displayName = "По рабочей группе";
// Фильтруем по РГ
- var filteredWG = templates.Where(t => string.Equals(t.WorkGroup, filterParam, StringComparison.OrdinalIgnoreCase)).ToList();
+ var filteredWG = templates.Where(t => string.Equals(t.WorkGroup, workGroup, StringComparison.OrdinalIgnoreCase)).ToList();
+ if (!string.IsNullOrWhiteSpace(responseArea))
+ {
+ // Дополнительно фильтруем по ЗО
+ filteredWG = filteredWG.Where(t => string.Equals(t.ResponseArea, responseArea, StringComparison.OrdinalIgnoreCase)).ToList();
+ }
// Получим все JobName из бд
var jobIds = filteredWG.Select(t => t.JobId).ToHashSet();
@@ -123,11 +134,12 @@ namespace PARR.Core.Services.Workload.Implementations
break;
}
+
var report = new WorkloadReport
{
Type = reportType,
DisplayName = displayName,
- SubfilterName = filterParam,
+ SubfilterName = string.Join(", ", new[] { responseArea, workGroup }),
ReportCacheCreatedAt = DateTimeOffset.UtcNow,
TemplateCacheCreatedAt = templates.FirstOrDefault()?.CacheDate ?? DateTimeOffset.MinValue,
Days = reportDays,
@@ -135,7 +147,7 @@ namespace PARR.Core.Services.Workload.Implementations
};
// Сохраним в кэш
- await workloadCacheService.SetWorkloadReport(report, reportType, dateStart, durationDays, offset, filterParam);
+ await workloadCacheService.SetWorkloadReport(report, reportType, dateStart, durationDays, offset, responseArea, workGroup);
logger.LogDebug("Отчет построен и сохранен в кэш");
return report;
@@ -181,8 +193,8 @@ namespace PARR.Core.Services.Workload.Implementations
WorkloadTemplateReportState state,
DateOnly date,
TimeSpan offset,
- WorkloadJobSubfliterType? jobSubfilterType,
- string? jobSubfilterValue
+ string? responseArea,
+ string? workGroup
)
{
if (string.IsNullOrWhiteSpace(filter))
@@ -243,8 +255,8 @@ namespace PARR.Core.Services.Workload.Implementations
if (dbTemplates.Count == 0)
return new List();
- // Если тип - работы, и нет дополнительных фильтров по ЗО или РГ, не полезем в кэш, вернем сразу
- if (reportType == WorkloadTemplateReportType.Job && (!jobSubfilterType.HasValue || string.IsNullOrWhiteSpace(jobSubfilterValue)))
+ // Если тип - работы, и нет дополнительных фильтров по ЗО и РГ, не полезем в кэш, вернем сразу
+ if (reportType == WorkloadTemplateReportType.Job && string.IsNullOrWhiteSpace(responseArea) && string.IsNullOrWhiteSpace(workGroup))
{
// Тут кэш не нужен, вернем сразу из бд (нет фильтров по РГ и ЗО, нужен список всех работ на дату)
return FilterWorkloadJobTemplateReport(dbTemplates, null);
@@ -259,41 +271,51 @@ namespace PARR.Core.Services.Workload.Implementations
if (dbTemplates.Count != cacheData.Count)
logger.LogWarning("Количество шаблонов в БД {DbCount} не совпадает с количеством шаблонов полученных из кэша {CacheCount}", dbTemplates.Count, cacheData.Count);
+ // Дополнительные фильтры
+ var trimmedResponseArea = responseArea?.Trim();
+ var trimmedWorkGroup = workGroup?.Trim();
+
// Фильтр по типу
switch (reportType)
{
case WorkloadTemplateReportType.Job:
// Смотрим есть ли дополнительные фильтры
- if (jobSubfilterType.HasValue && !string.IsNullOrWhiteSpace(jobSubfilterValue))
+ if (!string.IsNullOrEmpty(trimmedResponseArea) || !string.IsNullOrEmpty(trimmedWorkGroup))
{
- var cacheTemplatesJobs = new HashSet();
// Фильтруем из кэша
- var trimmedJobSubfilterValue = jobSubfilterValue.Trim();
- switch (jobSubfilterType)
- {
- case WorkloadJobSubfliterType.ResponseArea:
- cacheTemplatesJobs = cacheData.Where(t => string.Equals(t.Data.ResponseArea, trimmedJobSubfilterValue, StringComparison.OrdinalIgnoreCase))
- .Select(t => t.Data.TemplateId).ToHashSet();
- break;
- case WorkloadJobSubfliterType.WorkGroup:
- cacheTemplatesJobs = cacheData.Where(t => string.Equals(t.Data.WorkGroup, trimmedJobSubfilterValue, StringComparison.OrdinalIgnoreCase))
- .Select(t => t.Data.TemplateId).ToHashSet();
- break;
- default:
- throw new ArgumentException("Неверный тип фильтра", nameof(jobSubfilterType));
- }
+ IEnumerable cacheTemplatesJobs = cacheData;
- return FilterWorkloadJobTemplateReport(dbTemplates, cacheTemplatesJobs);
+ // Фильтруем по ЗО если есть
+ if (!string.IsNullOrEmpty(trimmedResponseArea))
+ cacheTemplatesJobs = cacheTemplatesJobs.Where(t => string.Equals(t.Data.ResponseArea, trimmedResponseArea, StringComparison.OrdinalIgnoreCase));
+
+ // Дополнительный фильтр по РГ, если есть
+ if (!string.IsNullOrEmpty(trimmedWorkGroup))
+ cacheTemplatesJobs = cacheTemplatesJobs.Where(t => string.Equals(t.Data.WorkGroup, trimmedWorkGroup, StringComparison.OrdinalIgnoreCase));
+
+ var filteredTemplateJobIds = cacheTemplatesJobs.Select(t => t.Data.TemplateId).ToHashSet();
+
+ return FilterWorkloadJobTemplateReport(dbTemplates, filteredTemplateJobIds);
}
return FilterWorkloadJobTemplateReport(dbTemplates, null);
case WorkloadTemplateReportType.WorkGroup:
- var cacheTemplatesWg = cacheData.Where(t => string.Equals(t.Data.WorkGroup, trimmedFilter, StringComparison.OrdinalIgnoreCase))
- .Select(t => t.Data.TemplateId).ToHashSet();
- return dbTemplates.Where(t => cacheTemplatesWg.Contains(t.TemplateId)).ToList();
+ var cacheTemplatesWg = cacheData.Where(t => string.Equals(t.Data.WorkGroup, trimmedFilter, StringComparison.OrdinalIgnoreCase));
+ // Дополнительный фильтр по ЗО, если есть
+ if (!string.IsNullOrEmpty(trimmedResponseArea))
+ cacheTemplatesWg = cacheTemplatesWg.Where(t => string.Equals(t.Data.ResponseArea, trimmedResponseArea, StringComparison.OrdinalIgnoreCase));
+
+ var filteredTemplateWgIds = cacheTemplatesWg.Select(t => t.Data.TemplateId).ToHashSet();
+
+ return dbTemplates.Where(t => filteredTemplateWgIds.Contains(t.TemplateId)).ToList();
case WorkloadTemplateReportType.ResponseArea:
- var cacheTemplatesRa = cacheData.Where(t => string.Equals(t.Data.ResponseArea, trimmedFilter, StringComparison.OrdinalIgnoreCase))
- .Select(t => t.Data.TemplateId).ToHashSet();
- return dbTemplates.Where(t => cacheTemplatesRa.Contains(t.TemplateId)).ToList();
+ var cacheTemplatesRa = cacheData.Where(t => string.Equals(t.Data.ResponseArea, trimmedFilter, StringComparison.OrdinalIgnoreCase));
+ // Дополнительный фильтр по РГ если есть
+ if (!string.IsNullOrEmpty(trimmedWorkGroup))
+ cacheTemplatesRa = cacheTemplatesRa.Where(t => string.Equals(t.Data.WorkGroup, trimmedWorkGroup, StringComparison.OrdinalIgnoreCase));
+
+ var filteredTemplateRaIds = cacheTemplatesRa.Select(t => t.Data.TemplateId).ToHashSet();
+
+ return dbTemplates.Where(t => filteredTemplateRaIds.Contains(t.TemplateId)).ToList();
}
logger.LogWarning("Не обработанный тип отчета {ReportType}", reportType);
diff --git a/PARR.Core/Services/Workload/Interfaces/IWorkloadService.cs b/PARR.Core/Services/Workload/Interfaces/IWorkloadService.cs
index 12c905f3..be742bed 100644
--- a/PARR.Core/Services/Workload/Interfaces/IWorkloadService.cs
+++ b/PARR.Core/Services/Workload/Interfaces/IWorkloadService.cs
@@ -1,4 +1,5 @@
-using PARR.Domain.DTOs.Workload;
+using Microsoft.Extensions.Primitives;
+using PARR.Domain.DTOs.Workload;
using PARR.Domain.Enums.Workload;
namespace PARR.Core.Services.Workload.Interfaces
@@ -21,9 +22,10 @@ namespace PARR.Core.Services.Workload.Interfaces
///
///
///
- ///
+ ///
+ ///
///
- Task GetWorkloadReportAsync(WorkloadReportType reportType, DateOnly dateStart, int durationDays, TimeSpan offset, string? filterParam);
+ Task GetWorkloadReportAsync(WorkloadReportType reportType, DateOnly dateStart, int durationDays, TimeSpan offset, string? responseArea, string? workGroup);
///
/// Получить отчет - список шаблонов на дату согласно фильтрам
@@ -33,10 +35,18 @@ namespace PARR.Core.Services.Workload.Interfaces
///
///
///
- /// Дополнительный фильтр при отчете по шаблонам, тип
- /// Дополнительный фильтр при отчете по шаблонам, значение
+ /// Дополнительный фильтр при отчете по шаблонам, ЗО
+ /// Дополнительный фильтр при отчете по шаблонам, РГ
///
- Task> GetTemplateReportAsync(WorkloadTemplateReportType reportType, string filter, WorkloadTemplateReportState state, DateOnly date, TimeSpan offset, WorkloadJobSubfliterType? jobSubfilterType, string? jobSubfilterValue);
+ Task> GetTemplateReportAsync(
+ WorkloadTemplateReportType reportType,
+ string filter,
+ WorkloadTemplateReportState state,
+ DateOnly date,
+ TimeSpan offset,
+ string? responseArea,
+ string? workGroup
+ );
}
}
diff --git a/PARR.Infrastructure/Redis/RedisCacheService.cs b/PARR.Infrastructure/Redis/RedisCacheService.cs
index 55f30ff5..3a3820d7 100644
--- a/PARR.Infrastructure/Redis/RedisCacheService.cs
+++ b/PARR.Infrastructure/Redis/RedisCacheService.cs
@@ -504,7 +504,7 @@ namespace PARR.Infrastructure.Redis
if (keyPartsToHash != null && keyPartsToHash.Length > 0)
{
- var partsToHashStr = string.Join(mainSeparator, keyPartsToHash)
+ var partsToHashStr = string.Join(mainSeparator, keyPartsToHash.Where(t => !string.IsNullOrWhiteSpace(t)))
.Replace(" ", wordSeparator)
.ToLower();