From 437f996ea88e37bc7ab3b926acf6bde095803825 Mon Sep 17 00:00:00 2001 From: Mikhail Trubnikov Date: Fri, 29 May 2026 17:09:36 +1000 Subject: [PATCH] =?UTF-8?q?feat(core):=20UnitService=20-=20=D1=80=D0=B0?= =?UTF-8?q?=D0=B1=D0=BE=D1=82=D0=B0=20=D1=81=20=D0=BA=D1=8D=D1=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- PARR.API/Controllers/V1/TestController.cs | 51 +++- .../Common/Interfaces/IRedisCacheService.cs | 18 +- PARR.Core/DependencyInjection.cs | 5 + .../Implementations/UnitCacheService.cs | 277 ++++++++++++++++++ .../Implementations/UnitService.cs | 40 +++ .../UnitService/Interfaces/IUnitService.cs | 35 +++ PARR.Domain/DTOs/UnitDto/UnitInfo.cs | 52 ++++ .../Redis/RedisCacheService.cs | 140 +++++++-- 8 files changed, 583 insertions(+), 35 deletions(-) create mode 100644 PARR.Core/Services/UnitService/Implementations/UnitCacheService.cs create mode 100644 PARR.Core/Services/UnitService/Implementations/UnitService.cs create mode 100644 PARR.Core/Services/UnitService/Interfaces/IUnitService.cs create mode 100644 PARR.Domain/DTOs/UnitDto/UnitInfo.cs diff --git a/PARR.API/Controllers/V1/TestController.cs b/PARR.API/Controllers/V1/TestController.cs index 886d5c13..39589063 100644 --- a/PARR.API/Controllers/V1/TestController.cs +++ b/PARR.API/Controllers/V1/TestController.cs @@ -1,5 +1,6 @@ using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; using PARR.API.Contracts.V1; using PARR.API.Contracts.V1.Responses.Base; using PARR.API.Controllers.V1.Base; @@ -7,7 +8,9 @@ using PARR.API.Services.Interfaces; using PARR.API.Settings; using PARR.Core.Common.Interfaces; using PARR.Core.Repositories.Interfaces; +using PARR.Core.Repositories.Interfaces.Unit; using PARR.Core.Services.NextRunServices; +using PARR.Core.Services.UnitService.Interfaces; using PARR.Core.Services.Workload.Implementations; using PARR.Domain.Cache; @@ -22,6 +25,8 @@ namespace PARR.API.Controllers.V1 private readonly ITemplateRepository templateService; private readonly WorkloadCacheService workloadCacheService; private readonly ILogger logger; + private readonly IUnitService unitService; + private readonly IUnitRepository unitRepository; public TestController( IClientService clientService, @@ -30,7 +35,9 @@ namespace PARR.API.Controllers.V1 ITemplateRepository templateService, MqSettings mqSettings, WorkloadCacheService workloadCacheService, - ILogger logger + ILogger logger, + IUnitService unitService, + IUnitRepository unitRepository ) { this.clientService = clientService; @@ -39,6 +46,8 @@ namespace PARR.API.Controllers.V1 this.templateService = templateService; this.workloadCacheService = workloadCacheService; this.logger = logger; + this.unitService = unitService; + this.unitRepository = unitRepository; } @@ -80,24 +89,38 @@ namespace PARR.API.Controllers.V1 /// /// [HttpPost(ApiRoutes.Test.CreateCache)] - public async Task CreateCache() + public async Task CreateCache([FromBody] Guid unitId) { - #region Test bucket id + var units = await unitRepository.Get().Take(100).Select(t => t.Id).ToListAsync(); + var listData = await unitService.GetWithCachingAsync(units); + //foreach(var unit in units) + //{ + // //поштучно + // var unitTtt = await unitService.GetWithCachingAsync(unit); + //} - var idList = new List(); + return Ok(); - for (int i = 0; i < 100; i++) - idList.Add( Guid.NewGuid()); - - foreach(var id in idList.OrderBy(t => t)) - { - var key = CacheKeys.Unit.UnitHashWithBucket(id); - var redisKey = redisCacheService.GetKey(key); + //var unit = await unitService.GetWithCachingAsync(unitId); - logger.LogDebug("Id: {Id}, key: '{Key}', redis key: '{RedisKey}'", id, key, redisKey); - } + //return Ok(unit); - #endregion + //#region Test bucket id + + //var idList = new List(); + + //for (int i = 0; i < 100; i++) + // idList.Add( Guid.NewGuid()); + + //foreach(var id in idList.OrderBy(t => t)) + //{ + // var key = CacheKeys.Unit.UnitHashWithBucket(id); + // var redisKey = redisCacheService.GetKey(key); + + // logger.LogDebug("Id: {Id}, key: '{Key}', redis key: '{RedisKey}'", id, key, redisKey); + //} + + //#endregion // var aaa = await workloadCacheService.GetTemplateReportDataAsync(); diff --git a/PARR.Core/Common/Interfaces/IRedisCacheService.cs b/PARR.Core/Common/Interfaces/IRedisCacheService.cs index 92c7ef3c..e5176fbd 100644 --- a/PARR.Core/Common/Interfaces/IRedisCacheService.cs +++ b/PARR.Core/Common/Interfaces/IRedisCacheService.cs @@ -82,8 +82,9 @@ /// /// /// Если указано, обновится у всего Hash. Если не указано и hash не существовал, создастся Hash с бесокнечным ttl + /// Сжимать данные /// - Task SetHashFieldAsync(string hashKey, string field, T value, TimeSpan? ttl = null); + Task SetHashFieldAsync(string hashKey, string field, T value, TimeSpan? ttl = null, bool useCompression = false); /// /// Получить значение поля из Hash @@ -92,7 +93,7 @@ /// /// /// - Task GetHashFieldAsync(string hashKey, string field); + Task GetHashFieldAsync(string hashKey, string field, bool useCompression = false); /// /// Получить случайную первую запись из Hash @@ -100,7 +101,7 @@ /// /// /// - Task<(string Field, T? Value)?> GetFirstHashFieldAsync(string hashKey); + Task<(string Field, T? Value)?> GetFirstHashFieldAsync(string hashKey, bool useCompression = false); /// /// Получить все значения из Hash @@ -108,7 +109,7 @@ /// /// /// - Task> GetAllHashFieldsAsync(string hashKey); + Task> GetAllHashFieldsAsync(string hashKey, bool useCompression = false); /// /// Удалить одну запись из Hash @@ -148,6 +149,15 @@ /// Task SetHashTtlAsync(string hashKey, TimeSpan ttl); + /// + /// Получить значения нескольких полей из Hash + /// + /// + /// + /// + /// + Task> GetHashFieldsAsync(List<(string HashKey, string FieldKey)> keys, bool useCompression = false); + #endregion } } diff --git a/PARR.Core/DependencyInjection.cs b/PARR.Core/DependencyInjection.cs index 8de67dc6..a1ffe502 100644 --- a/PARR.Core/DependencyInjection.cs +++ b/PARR.Core/DependencyInjection.cs @@ -19,6 +19,8 @@ using PARR.Core.Services.TaskServices.Providers; using PARR.Core.Services.TaskServices.ReconciliationHosted; using PARR.Core.Services.UnitFilterService; using PARR.Core.Services.UnitFilterService.Models; +using PARR.Core.Services.UnitService.Implementations; +using PARR.Core.Services.UnitService.Interfaces; using PARR.Core.Services.Workload.Implementations; using PARR.Core.Services.Workload.Interfaces; using PARR.Domain.Enums; @@ -103,6 +105,9 @@ namespace PARR.Core services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + //services.AddScoped(); #endregion diff --git a/PARR.Core/Services/UnitService/Implementations/UnitCacheService.cs b/PARR.Core/Services/UnitService/Implementations/UnitCacheService.cs new file mode 100644 index 00000000..cd608135 --- /dev/null +++ b/PARR.Core/Services/UnitService/Implementations/UnitCacheService.cs @@ -0,0 +1,277 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; +using PARR.Core.Common.Interfaces; +using PARR.Core.Repositories.Interfaces.Unit; +using PARR.Domain.Cache; +using PARR.Domain.DTOs.UnitDto; + +namespace PARR.Core.Services.UnitService.Implementations +{ + /// + /// Сервис по работе с юнитами из кэш и бд + /// + internal class UnitCacheService + { + /// + /// TTL для кэш + /// + private readonly TimeSpan CacheTtl = TimeSpan.FromHours(24); + + private readonly ILogger logger; + private readonly IUnitRepository unitRepository; + private readonly IRedisCacheService redisCacheService; + + public UnitCacheService( + ILogger logger, + IUnitRepository unitRepository, + IRedisCacheService redisCacheService + ) + { + this.logger = logger; + this.unitRepository = unitRepository; + this.redisCacheService = redisCacheService; + } + + /// + /// Получить UnitInfo по id + /// + /// + /// + public async Task GetAsync(Guid id) + { + var hashKey = redisCacheService.GetKey(CacheKeys.Unit.UnitHashWithBucket(id)); + var unitKey = redisCacheService.GetKey(CacheKeys.Unit.UnitItem(id)); + + // Смотрим, есть ли в кэш + var cache = await redisCacheService.GetHashFieldAsync(hashKey, unitKey, true); + if (cache != null) + { + logger.LogDebug("Данные о юните {UnitId} получены из кэша.", id); + return cache; + } + + // Нет в кэш, ищем в бд + var dbData = await GetUnitAsync(id); + if (dbData == null) + { + logger.LogWarning("В БД нет данных о юните {UnitId}. Вернулся null", id); + return null; + } + + // Сохраняем в кэш + await redisCacheService.SetHashFieldAsync(hashKey, unitKey, dbData, CacheTtl, true); + + logger.LogDebug("Вернули юнит полученный из БД {UnitId}.", id); + + return dbData; + } + + /// + /// Получить словарь UnitInfo + /// + /// + /// + public async Task> GetAsync(IReadOnlySet ids) + { + if (ids == null || ids.Count == 0) + return new Dictionary(); + + // Формируем ключи + var keys = ids.Select(t => + { + var (hashKey, fieldKey) = (redisCacheService.GetKey(CacheKeys.Unit.UnitHashWithBucket(t)), redisCacheService.GetKey(CacheKeys.Unit.UnitItem(t))); + return (HashKey: hashKey, FieldKey: fieldKey); + }).ToList(); + + // Получаем из кэш + var cacheData = await redisCacheService.GetHashFieldsAsync(keys, true); + + // проверить, если получили все элементы, вернуть + // если не все, докачать из бд + // сохранить докаченные в кэш + ттл + не забыть указать сжатие (может вообще этот метод вынести отдельно, так как пересекается когда по одному) + + //var dbData = await GetUnitsAsync(ids); + + + + //todo: + + return null; + } + + /// + /// Удалить запись UnitInfo из кэш + /// + /// + /// + /// + public Task RemoveFromCacheAsync(Guid id) + { + //todo: может удалять тоже списком + + throw new NotImplementedException(); + } + + + /// + /// Получить значение по одному юниту из БД + /// + /// + /// + private async Task GetUnitAsync(Guid unitId) + { + var unit = await unitRepository.Get() + .AsNoTracking() + .Select(t => new + { + Id = t.Id, + Name = t.Name, + Values = t.UnitValues.Select(x => new { FieldId = x.FieldId, Value = x.Value!.Value }), + ParentUnits = t.ParentUnits.Select(t => t.ParentUnitId), + ChildUnits = t.ChildUnits.Select(t => t.ChildUnitId) + }) + .FirstOrDefaultAsync(t => t.Id == unitId); + + if (unit == null) + return null; + + // Склеим вместе родителей и детей, чтоб не делать лишних запросов к бд + var relatives = unit.ParentUnits.Union(unit.ChildUnits).ToList(); + + // Получаем базовую инфу о родственниках + var relativesDictionary = await unitRepository.Get() + .AsNoTracking() + .Where(t => relatives.Contains(t.Id)) + .Select(t => new UnitInfoBase + { + Id = t.Id, + Name = t.Name, + Values = t.UnitValues.Select(x => new UnitInfoAttribute + { + FieldId = x.FieldId, + Value = x.Value!.Value + }).ToList() + }).ToDictionaryAsync(t => t.Id); + + // Собираем итоговый UnitInfo + return new UnitInfo + { + Id = unit.Id, + Name = unit.Name, + Values = unit.Values.Select(x => new UnitInfoAttribute { FieldId = x.FieldId, Value = x.Value }).ToList(), + ParentUnits = unit.ParentUnits + .Where(id => relativesDictionary.ContainsKey(id)) + .Select(id => relativesDictionary[id]) + .ToList(), + ChildUnits = unit.ChildUnits + .Where(id => relativesDictionary.ContainsKey(id)) + .Select(id => relativesDictionary[id]) + .ToList() + }; + } + + /// + /// Получить значения по списку юнитов + /// + /// + /// + private async Task> GetUnitsAsync(IReadOnlySet ids) + { + if (ids == null || ids.Count == 0) + return new List(); + + // Пакетно получаем основные данные для юнитов + var dbUnits = new List(); + + // Выполняем порционно + foreach (var chank in ids.Chunk(500)) + { + var chunkUnits = await unitRepository.Get() + .AsNoTracking() + .Where(t => chank.Contains(t.Id)) + .Select(t => new + { + Id = t.Id, + Name = t.Name, + Values = t.UnitValues.Select(x => new { FieldId = x.FieldId, Value = x.Value!.Value }).ToList(), + ParentUnits = t.ParentUnits.Select(p => p.ParentUnitId).ToList(), + ChildUnits = t.ChildUnits.Select(c => c.ChildUnitId).ToList() + }) + .ToListAsync(); + + dbUnits.AddRange(chunkUnits); + } + + if (dbUnits.Count == 0) + return new List(); + + // Собираем ВСЕ ID родственников(и родителей, и детей) для ВСЕХ найденных юнитов в один HashSet + var allRelativeIds = new HashSet(); + foreach (var unit in dbUnits) + { + foreach (var parentId in unit.ParentUnits) + allRelativeIds.Add(parentId); + + foreach (var childId in unit.ChildUnits) + allRelativeIds.Add(childId); + } + + // Пакетно загружаем базовую информацию обо всех родственниках за один раз + var relativesDictionary = new Dictionary(); + + foreach (var chunk in allRelativeIds.Chunk(500)) + { + var chunkRelatives = await unitRepository.Get() + .AsNoTracking() + .Where(t => chunk.Contains(t.Id)) + .Select(t => new UnitInfoBase + { + Id = t.Id, + Name = t.Name, + Values = t.UnitValues.Select(x => new UnitInfoAttribute + { + FieldId = x.FieldId, + Value = x.Value!.Value + }).ToList() + }) + .ToDictionaryAsync(t => t.Id); + + foreach (var kvp in chunkRelatives) + { + relativesDictionary[kvp.Key] = kvp.Value; + } + } + + // Собираем итоговый список UnitInfo, маппим родственников из словаря + var result = new List(dbUnits.Count); + foreach (var unit in dbUnits) + { + var unitInfo = new UnitInfo + { + Id = unit.Id, + Name = unit.Name, + Values = ((IEnumerable)unit.Values) + .Select(x => new UnitInfoAttribute { FieldId = x.FieldId, Value = x.Value }) + .ToList(), + + ParentUnits = ((IEnumerable)unit.ParentUnits) + .Where(id => relativesDictionary.ContainsKey(id)) + .Select(id => relativesDictionary[id]) + .ToList(), + + ChildUnits = ((IEnumerable)unit.ChildUnits) + .Where(id => relativesDictionary.ContainsKey(id)) + .Select(id => relativesDictionary[id]) + .ToList() + }; + + result.Add(unitInfo); + } + + logger.LogDebug("Получено из БД данных для {Count} юнитов", result.Count); + + return result; + } + + } +} diff --git a/PARR.Core/Services/UnitService/Implementations/UnitService.cs b/PARR.Core/Services/UnitService/Implementations/UnitService.cs new file mode 100644 index 00000000..5bd287ed --- /dev/null +++ b/PARR.Core/Services/UnitService/Implementations/UnitService.cs @@ -0,0 +1,40 @@ +using Microsoft.Extensions.Logging; +using PARR.Core.Services.UnitService.Interfaces; +using PARR.Domain.DTOs.UnitDto; + +namespace PARR.Core.Services.UnitService.Implementations +{ + internal class UnitService : IUnitService + { + private readonly ILogger logger; + private readonly UnitCacheService unitCacheService; + + public UnitService( + ILogger logger, + UnitCacheService unitCacheService + ) + { + this.logger = logger; + this.unitCacheService = unitCacheService; + } + + + public async Task GetWithCachingAsync(Guid id) + { + return await unitCacheService.GetAsync(id); + } + + public async Task> GetWithCachingAsync(IEnumerable ids) + { + // Защищаем себя от Multiple Enumeration и убираем дубликаты, если передали List + var uniqueIds = ids as IReadOnlySet ?? ids.ToHashSet(); + + return await unitCacheService.GetAsync(uniqueIds); + } + + public async Task RemoveFromCacheAsync(Guid id) + { + await unitCacheService.RemoveFromCacheAsync(id); + } + } +} diff --git a/PARR.Core/Services/UnitService/Interfaces/IUnitService.cs b/PARR.Core/Services/UnitService/Interfaces/IUnitService.cs new file mode 100644 index 00000000..fa621213 --- /dev/null +++ b/PARR.Core/Services/UnitService/Interfaces/IUnitService.cs @@ -0,0 +1,35 @@ +using PARR.Domain.DTOs.UnitDto; + +namespace PARR.Core.Services.UnitService.Interfaces +{ + /// + /// Сервис управления юнитами + /// + public interface IUnitService + { + /// + /// Получить юнит со всем значениями. + /// Используется кэш. + /// + /// + /// + Task GetWithCachingAsync(Guid id); + + /// + /// Получить словарь юнитов со всеми значениями. + /// Используется кэш. + /// + /// + /// + Task> GetWithCachingAsync(IEnumerable ids); + + /// + /// Удалить юнит из кэш. + /// + /// + /// + Task RemoveFromCacheAsync(Guid id); + + + } +} diff --git a/PARR.Domain/DTOs/UnitDto/UnitInfo.cs b/PARR.Domain/DTOs/UnitDto/UnitInfo.cs new file mode 100644 index 00000000..f9706a1e --- /dev/null +++ b/PARR.Domain/DTOs/UnitDto/UnitInfo.cs @@ -0,0 +1,52 @@ +namespace PARR.Domain.DTOs.UnitDto +{ + /// + /// Юнит с ЕСПП атрибутами, детьми и родителями и их атрибутами + /// + public record UnitInfo : UnitInfoBase + { + /// + /// Родительские юниты + /// + public IReadOnlyCollection ParentUnits { get; init; } = []; + + /// + /// Дочерние юниты + /// + public IReadOnlyCollection ChildUnits { get; init; } = []; + } + + public record UnitInfoBase + { + /// + /// Id юнита + /// + public Guid Id { get; init; } + + /// + /// ЭК + /// + public required string Name { get; init; } + + /// + /// Атрибуты из ЕСПП + /// + public IReadOnlyCollection Values { get; init; } = []; + } + + /// + /// Атрибут ЕСПП + /// + public record UnitInfoAttribute + { + /// + /// ИД поля + /// + public Guid FieldId { get; init; } + + /// + /// Значение + /// + public string? Value { get; init; } + } +} diff --git a/PARR.Infrastructure/Redis/RedisCacheService.cs b/PARR.Infrastructure/Redis/RedisCacheService.cs index 81616f31..3ef715c1 100644 --- a/PARR.Infrastructure/Redis/RedisCacheService.cs +++ b/PARR.Infrastructure/Redis/RedisCacheService.cs @@ -1,5 +1,6 @@ using Microsoft.Extensions.Caching.Distributed; using Microsoft.Extensions.Logging; +using Newtonsoft.Json.Linq; using PARR.Core.Common.Interfaces; using PARR.Infrastructure.Redis.Helpers; using StackExchange.Redis; @@ -48,7 +49,7 @@ namespace PARR.Infrastructure.Redis } catch (Exception ex) { - throw new Exception("Формат данных не байты.", ex); + throw new InvalidOperationException("Формат данных не байты.", ex); } } else @@ -65,7 +66,7 @@ namespace PARR.Infrastructure.Redis } catch (Exception ex) { - throw new Exception("Формат данных не строка.", ex); + throw new InvalidOperationException("Формат данных не строка.", ex); } } @@ -105,8 +106,8 @@ namespace PARR.Infrastructure.Redis if (useCompression) { // храним в байтах, если надо то сжимаем - var jsonData = SerializeWithCompression(data); - await cache.SetAsync(key, jsonData, options); + var byteData = SerializeWithCompression(data); + await cache.SetAsync(key, byteData, options); } else { @@ -187,14 +188,24 @@ namespace PARR.Infrastructure.Redis #region Нативные операции Redis, Redis Hash - public async Task SetHashFieldAsync(string hashKey, string field, T value, TimeSpan? ttl = null) + public async Task SetHashFieldAsync(string hashKey, string field, T value, TimeSpan? ttl = null, bool useCompression = false) { // меняет одно поле в Hash - var jsonData = JsonSerializer.Serialize(value); - - // true - поля не было, создалось новое. false - поле было, обновили значение - var result = await redis.HashSetAsync(hashKey, field, jsonData); + if (useCompression) + { + // храним в байтах, если надо то сжимаем + var byteData = SerializeWithCompression(value); + // true - поля не было, создалось новое. false - поле было, обновили значение + var result = await redis.HashSetAsync(hashKey, field, byteData); + } + else + { + // храним в строке + var jsonData = JsonSerializer.Serialize(value); + // true - поля не было, создалось новое. false - поле было, обновили значение + var result = await redis.HashSetAsync(hashKey, field, jsonData); + } // если указан ttl, обновим для всего Hash // если не указан и ранее был создан hashKey, оставит его ttl; а если hashKey не было, то создаст его БЕССРОЧНЫМ!!! @@ -202,7 +213,7 @@ namespace PARR.Infrastructure.Redis await SetHashTtlAsync(hashKey, ttl.Value); } - public async Task GetHashFieldAsync(string hashKey, string field) + public async Task GetHashFieldAsync(string hashKey, string field, bool useCompression = false) { // получить значение поля из Hash var value = await redis.HashGetAsync(hashKey, field); @@ -210,10 +221,80 @@ namespace PARR.Infrastructure.Redis if (value.IsNullOrEmpty) return default; - return JsonSerializer.Deserialize(value); + if (useCompression) + { + // хранили в байтах + try + { + return DeserializeWithCompression(value); + } + catch (Exception ex) + { + throw new InvalidOperationException("Формат данных не байты.", ex); + } + } + else + { + // хранили в строке + return JsonSerializer.Deserialize(value); + } } - public async Task<(string Field, T? Value)?> GetFirstHashFieldAsync(string hashKey) + public async Task> GetHashFieldsAsync(List<(string HashKey, string FieldKey)> keys, bool useCompression = false) + { + var allItems = new List(keys.Count); + + // Делим на порции по 5000 (оптимально) + foreach (var chunk in keys.Chunk(5000)) + { + // Формируем пайплайн ТОЛЬКО для 5 000 элементов + var tasks = new List>(chunk.Length); + foreach (var key in chunk) + { + tasks.Add(redis.HashGetAsync(key.HashKey, key.FieldKey)); + } + + logger.LogDebug("Сформировал {Count} одновременных запросов в кэш", tasks.Count); + + // Ждем ответа от текущей порции + RedisValue[] results = await Task.WhenAll(tasks); + + // Парсим + foreach (var result in results) + { + if (result.HasValue) + { + if (useCompression) + { + try + { + var item = DeserializeWithCompression((byte[])result); + if (item != null) + allItems.Add(item); + } + catch (Exception ex) + { + throw new InvalidOperationException("Формат данных не байты.", ex); + } + } + else + { + var item = JsonSerializer.Deserialize(result.ToString()); + if (item != null) + allItems.Add(item); + } + + } + } + + } + + logger.LogDebug("Получил {Count} элементов из кэш.", allItems.Count); + + return allItems; + } + + public async Task<(string Field, T? Value)?> GetFirstHashFieldAsync(string hashKey, bool useCompression = false) { logger.LogDebug("Запрос первого поля из хеша '{HashKey}'", hashKey); @@ -234,7 +315,24 @@ namespace PARR.Infrastructure.Redis continue; } - var deserialized = JsonSerializer.Deserialize(entry.Value); + T? deserialized = default; + + if (useCompression) + { + // хранили в байтах + try + { + deserialized = DeserializeWithCompression(entry.Value); + } + catch (Exception ex) + { + throw new InvalidOperationException("Формат данных не байты.", ex); + } + } + else + { + deserialized = JsonSerializer.Deserialize(entry.Value); + } logger.LogDebug("Получено поле '{Field}' из хеша '{HashKey}', тип: {Type}", entry.Name, hashKey, typeof(T).Name); @@ -245,7 +343,7 @@ namespace PARR.Infrastructure.Redis return null; } - public async Task> GetAllHashFieldsAsync(string hashKey) + public async Task> GetAllHashFieldsAsync(string hashKey, bool useCompression = false) { // получить все записи из Hash var objs = await redis.HashGetAllAsync(hashKey); @@ -253,12 +351,20 @@ namespace PARR.Infrastructure.Redis if (objs.Length == 0) return new Dictionary(); - var result = objs.ToDictionary( + try + { + var result = objs.ToDictionary( t => t.Name.ToString(), - t => JsonSerializer.Deserialize(t.Value) + t => useCompression ? DeserializeWithCompression(t.Value) : JsonSerializer.Deserialize(t.Value) ); - return result!; + return result!; + } + catch (Exception ex) + { + throw new Exception("Не смог десериализовать полученные данные", ex); + //return new Dictionary(); + } } public async Task DeleteHashFieldAsync(string hashKey, string field)