feat(core, infrastructure): UnitService - удаление/массовое удаление юнитов

This commit is contained in:
Mikhail Trubnikov
2026-06-01 16:10:00 +10:00
parent bdcc858bc5
commit d27d594e57
6 changed files with 80 additions and 7 deletions

View File

@@ -91,14 +91,21 @@ namespace PARR.API.Controllers.V1
[HttpPost(ApiRoutes.Test.CreateCache)] [HttpPost(ApiRoutes.Test.CreateCache)]
public async Task<IActionResult> CreateCache([FromBody] Guid unitId) public async Task<IActionResult> CreateCache([FromBody] Guid unitId)
{ {
var units = await unitRepository.Get().Take(100).Select(t => t.Id).ToListAsync(); //var units = await unitRepository.Get().Take(100).Select(t => t.Id).ToListAsync();
var listData = await unitService.GetWithCachingAsync(units); //var listData = await unitService.GetWithCachingAsync(units);
//foreach(var unit in units) //foreach(var unit in units)
//{ //{
// //поштучно // //поштучно
// var unitTtt = await unitService.GetWithCachingAsync(unit); // var unitTtt = await unitService.GetWithCachingAsync(unit);
//} //}
// удалить элемент
//await unitService.RemoveFromCacheAsync(Guid.Parse("79833f20-a84f-45f4-a4ed-0dae09b683d7"));
// todo: проверить удаление пачки юнитов !!!!!!!!!!!!!!!!!!!!!
//await unitService.RemoveFromCacheAsync(units);
return Ok(); return Ok();
//var unit = await unitService.GetWithCachingAsync(unitId); //var unit = await unitService.GetWithCachingAsync(unitId);

View File

@@ -158,6 +158,13 @@
/// <returns></returns> /// <returns></returns>
Task<List<T>> GetHashFieldsAsync<T>(List<(string HashKey, string FieldKey)> keys, bool useCompression = false); Task<List<T>> GetHashFieldsAsync<T>(List<(string HashKey, string FieldKey)> keys, bool useCompression = false);
/// <summary>
/// Удалить список из Hash
/// </summary>
/// <param name="keys"></param>
/// <returns></returns>
Task DeleteHashFieldsAsync(List<(string HashKey, string FieldKey)> keys);
#endregion #endregion
} }
} }

View File

@@ -125,11 +125,32 @@ namespace PARR.Core.Services.UnitService.Implementations
/// <param name="id"></param> /// <param name="id"></param>
/// <returns></returns> /// <returns></returns>
/// <exception cref="NotImplementedException"></exception> /// <exception cref="NotImplementedException"></exception>
public Task RemoveFromCacheAsync(Guid id) public async Task RemoveFromCacheAsync(Guid id)
{ {
//todo: может удалять тоже списком var hashKey = redisCacheService.GetKey(CacheKeys.Unit.UnitHashWithBucket(id));
var unitKey = redisCacheService.GetKey(CacheKeys.Unit.UnitItem(id));
throw new NotImplementedException(); await redisCacheService.DeleteHashFieldAsync(hashKey, unitKey);
}
/// <summary>
/// Удалить записи UnitInfo из кэш
/// </summary>
/// <param name="ids"></param>
/// <returns></returns>
public async Task RemoveFromCacheAsync(IReadOnlySet<Guid> ids)
{
if (ids == null || ids.Count == 0)
return;
// Формируем ключи
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();
await redisCacheService.DeleteHashFieldsAsync(keys);
} }

View File

@@ -36,5 +36,13 @@ namespace PARR.Core.Services.UnitService.Implementations
{ {
await unitCacheService.RemoveFromCacheAsync(id); await unitCacheService.RemoveFromCacheAsync(id);
} }
public async Task RemoveFromCacheAsync(IEnumerable<Guid> ids)
{
// Защищаем себя от Multiple Enumeration и убираем дубликаты, если передали List
var uniqueIds = ids as IReadOnlySet<Guid> ?? ids.ToHashSet();
await unitCacheService.RemoveFromCacheAsync(uniqueIds);
}
} }
} }

View File

@@ -30,6 +30,11 @@ namespace PARR.Core.Services.UnitService.Interfaces
/// <returns></returns> /// <returns></returns>
Task RemoveFromCacheAsync(Guid id); Task RemoveFromCacheAsync(Guid id);
/// <summary>
/// Удалить юниты из кэша
/// </summary>
/// <param name="ids"></param>
/// <returns></returns>
Task RemoveFromCacheAsync(IEnumerable<Guid> ids);
} }
} }

View File

@@ -1,6 +1,5 @@
using Microsoft.Extensions.Caching.Distributed; using Microsoft.Extensions.Caching.Distributed;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Newtonsoft.Json.Linq;
using PARR.Core.Common.Interfaces; using PARR.Core.Common.Interfaces;
using PARR.Infrastructure.Redis.Helpers; using PARR.Infrastructure.Redis.Helpers;
using StackExchange.Redis; using StackExchange.Redis;
@@ -376,6 +375,32 @@ namespace PARR.Infrastructure.Redis
await redis.HashDeleteAsync(hashKey, field); await redis.HashDeleteAsync(hashKey, field);
} }
public async Task DeleteHashFieldsAsync(List<(string HashKey, string FieldKey)> keys)
{
if (keys == null || keys.Count == 0)
return;
int deletedCount = 0;
// Делим на порции по 5000, чтобы не перегружать буфер команд Redis
foreach (var chunk in keys.Chunk(5000))
{
var tasks = new List<Task<bool>>(chunk.Length);
foreach (var key in chunk)
tasks.Add(redis.HashDeleteAsync(key.HashKey, key.FieldKey));
logger.LogDebug("Отправил пачку из {Count} команд на удаление в Redis", tasks.Count);
// Ждем выполнения текущей порции команд удалений
bool[] results = await Task.WhenAll(tasks);
// Считаем, сколько элементов реально было удалено
deletedCount += results.Count(x => x == true);
}
logger.LogDebug("Успешно удалено элементов из кэша: {Count}.", deletedCount);
}
public async Task<bool> HashFieldExistsAsync(string hashKey, string field) public async Task<bool> HashFieldExistsAsync(string hashKey, string field)
{ {
// есть ли запись в Hash // есть ли запись в Hash