EV.MemoryCache
1.0.0
dotnet add package EV.MemoryCache --version 1.0.0
NuGet\Install-Package EV.MemoryCache -Version 1.0.0
<PackageReference Include="EV.MemoryCache" Version="1.0.0" />
<PackageVersion Include="EV.MemoryCache" Version="1.0.0" />
<PackageReference Include="EV.MemoryCache" />
paket add EV.MemoryCache --version 1.0.0
#r "nuget: EV.MemoryCache, 1.0.0"
#:package EV.MemoryCache@1.0.0
#addin nuget:?package=EV.MemoryCache&version=1.0.0
#tool nuget:?package=EV.MemoryCache&version=1.0.0
EV.MemoryCache
Librería de Cache en Memoria de Alto Rendimiento: Una librería simple y eficiente para .NET 8 que gestiona el cache en memoria usando IMemoryCache
con opciones de configuración avanzadas y logging comprehensivo.
Características
- Integración Fácil: Configuración simple con inyección de dependencias
- Expiración Configurable: Soporte para tiempos de expiración absolutos y deslizantes
- Gestión de Memoria: Límites de tamaño y compactación automática
- Logging Comprehensivo: Logging detallado con niveles configurables
- Validación: Validación de configuración integrada
- Optimizado para Rendimiento: Operaciones síncronas para acceso rápido en memoria
- Listo para Producción: Manejo de errores y degradación elegante
Instalación
dotnet add package EV.MemoryCache
Inicio Rápido
1. Configuración (appsettings.json)
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning",
"EV.MemoryCache": "Debug"
}
},
"AllowedHosts": "*",
"MemoryCacheConfiguration": {
"Enabled": true,
"DefaultExpirationMinutes": 30,
"DefaultSlidingExpirationMinutes": 5,
"SizeLimit": 3000,
"CompactionPercentage": 0.25
}
}
2. Registro de Servicios (Program.cs)
using EV.MemoryCache;
var builder = WebApplication.CreateBuilder(args);
// Agregar servicios al contenedor
builder.Services.AddControllers();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
// Registrar el servicio de EV.MemoryCache
builder.Services.AddMemoryCacheService(builder.Configuration);
var app = builder.Build();
// Configurar el pipeline
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
app.UseHttpsRedirection();
app.UseAuthorization();
app.MapControllers();
app.Run();
3. Uso en Servicios
using EV.MemoryCache.Interfaces;
public class ProductService
{
private readonly IMemoryCacheManager _cache;
private readonly IProductRepository _repository;
public ProductService(IMemoryCacheManager cache, IProductRepository repository)
{
_cache = cache;
_repository = repository;
}
public async Task<Product?> GetProductAsync(int productId)
{
var cacheKey = $"product:{productId}";
// Usar GetOrSet para cache automático
return _cache.GetOrSet(cacheKey, () =>
{
// Esto solo se ejecuta si no está en cache
return _repository.GetById(productId);
}, TimeSpan.FromMinutes(15));
}
public void UpdateProduct(Product product)
{
_repository.Update(product);
// Actualizar cache con datos frescos
var cacheKey = $"product:{product.Id}";
_cache.Set(cacheKey, product);
}
public void DeleteProduct(int productId)
{
_repository.Delete(productId);
// Remover del cache
_cache.Remove($"product:{productId}");
}
}
Opciones de Configuración
Propiedades de MemoryCacheConfiguration
Propiedad | Tipo | Por Defecto | Descripción |
---|---|---|---|
Enabled |
bool | true | Habilitar/deshabilitar cache globalmente |
DefaultExpirationMinutes |
int | 30 | Tiempo de expiración absoluto por defecto |
DefaultSlidingExpirationMinutes |
int? | null | Tiempo de expiración deslizante por defecto |
SizeLimit |
long? | null | Número máximo de entradas en cache |
CompactionPercentage |
double? | null | Porcentaje a remover cuando se alcanza el límite (0.0-1.0) |
Ejemplos de Configuración
Ambiente de Desarrollo
{
"MemoryCacheConfiguration": {
"Enabled": true,
"DefaultExpirationMinutes": 5,
"DefaultSlidingExpirationMinutes": 2,
"SizeLimit": 100
}
}
Ambiente de Producción
{
"MemoryCacheConfiguration": {
"Enabled": true,
"DefaultExpirationMinutes": 60,
"DefaultSlidingExpirationMinutes": 15,
"SizeLimit": 50000,
"CompactionPercentage": 0.20
}
}
Cache Deshabilitado (para testing)
{
"MemoryCacheConfiguration": {
"Enabled": false
}
}
Referencia de API
IMemoryCacheManager
Método | Descripción | Parámetros |
---|---|---|
Get<T>(key) |
Obtiene un elemento del cache | string key |
Set<T>(key, value, expiration?) |
Almacena un elemento en cache | string key, T value, TimeSpan? expiration |
GetOrSet<T>(key, factory, expiration?) |
Obtiene del cache o crea usando factory | string key, Func<T> factory, TimeSpan? expiration |
Exists(key) |
Verifica si existe una clave en cache | string key |
Remove(key) |
Remueve un elemento del cache | string key |
Patrones de Uso
Operaciones Básicas de Cache
// Almacenar en cache
_cache.Set("user:123", userObject, TimeSpan.FromMinutes(30));
// Obtener del cache
var user = _cache.Get<User>("user:123");
// Verificar si existe
if (_cache.Exists("user:123"))
{
// La clave existe en cache
}
// Remover del cache
_cache.Remove("user:123");
Patrón Get-or-Set (Recomendado)
// Patrón más eficiente - maneja cache miss automáticamente
var user = _cache.GetOrSet("user:123", () =>
{
// Esto solo se ejecuta si no está en cache
return LoadUserFromDatabase(123);
}, TimeSpan.FromMinutes(30));
Uso Avanzado
Implementación en Controladores
[ApiController]
[Route("api/[controller]")]
public class UsersController : ControllerBase
{
private readonly IMemoryCacheManager _cache;
private readonly IUserService _userService;
private readonly ILogger<UsersController> _logger;
public UsersController(
IMemoryCacheManager cache,
IUserService userService,
ILogger<UsersController> logger)
{
_cache = cache;
_userService = userService;
_logger = logger;
}
[HttpGet("{id}")]
public ActionResult<User> GetUser(int id)
{
try
{
var cacheKey = $"user:{id}";
var user = _cache.GetOrSet(cacheKey, () =>
{
_logger.LogInformation("Loading user {UserId} from service", id);
return _userService.GetUserById(id);
}, TimeSpan.FromMinutes(15));
return user != null ? Ok(user) : NotFound();
}
catch (Exception ex)
{
_logger.LogError(ex, "Error retrieving user {UserId}", id);
return StatusCode(500, "Internal server error");
}
}
[HttpPut("{id}")]
public ActionResult UpdateUser(int id, [FromBody] User user)
{
try
{
_userService.UpdateUser(user);
// Actualizar cache con datos frescos
var cacheKey = $"user:{id}";
_cache.Set(cacheKey, user, TimeSpan.FromMinutes(15));
return Ok(user);
}
catch (Exception ex)
{
_logger.LogError(ex, "Error updating user {UserId}", id);
return StatusCode(500, "Internal server error");
}
}
[HttpDelete("{id}")]
public ActionResult DeleteUser(int id)
{
try
{
_userService.DeleteUser(id);
// Remover del cache
_cache.Remove($"user:{id}");
return NoContent();
}
catch (Exception ex)
{
_logger.LogError(ex, "Error deleting user {UserId}", id);
return StatusCode(500, "Internal server error");
}
}
[HttpPost("cache/clear/{id}")]
public ActionResult ClearUserCache(int id)
{
try
{
var cacheKey = $"user:{id}";
var existed = _cache.Exists(cacheKey);
if (existed)
{
_cache.Remove(cacheKey);
_logger.LogInformation("Cache cleared for user {UserId}", id);
return Ok(new { Message = $"Cache cleared for user {id}" });
}
return Ok(new { Message = $"User {id} was not cached" });
}
catch (Exception ex)
{
_logger.LogError(ex, "Error clearing cache for user {UserId}", id);
return StatusCode(500, "Internal server error");
}
}
}
Capa de Servicio con Cache
public class ProductService : IProductService
{
private readonly IMemoryCacheManager _cache;
private readonly IProductRepository _repository;
private readonly ILogger<ProductService> _logger;
public ProductService(
IMemoryCacheManager cache,
IProductRepository repository,
ILogger<ProductService> logger)
{
_cache = cache;
_repository = repository;
_logger = logger;
}
public Product? GetProductById(int id)
{
return _cache.GetOrSet($"product:{id}", () =>
{
_logger.LogDebug("Loading product {ProductId} from repository", id);
return _repository.GetById(id);
});
}
public List<Product> GetProductsByCategory(int categoryId)
{
return _cache.GetOrSet($"products:category:{categoryId}", () =>
{
_logger.LogDebug("Loading products for category {CategoryId}", categoryId);
return _repository.GetByCategory(categoryId);
}, TimeSpan.FromMinutes(10));
}
public List<Product> GetPopularProducts()
{
return _cache.GetOrSet("products:popular", () =>
{
_logger.LogDebug("Loading popular products from repository");
return _repository.GetPopularProducts();
}, TimeSpan.FromMinutes(5)); // Cache más corto para datos populares
}
public void UpdateProduct(Product product)
{
_repository.Update(product);
// Actualizar cache del producto individual
_cache.Set($"product:{product.Id}", product);
// Limpiar cache de categoría para asegurar consistencia
_cache.Remove($"products:category:{product.CategoryId}");
// Limpiar cache de productos populares
_cache.Remove("products:popular");
_logger.LogInformation("Product {ProductId} updated and cache refreshed", product.Id);
}
public void DeleteProduct(int productId)
{
var product = GetProductById(productId);
if (product == null) return;
_repository.Delete(productId);
// Limpiar todos los caches relacionados
_cache.Remove($"product:{productId}");
_cache.Remove($"products:category:{product.CategoryId}");
_cache.Remove("products:popular");
_logger.LogInformation("Product {ProductId} deleted and cache cleared", productId);
}
public bool ProductExists(int id)
{
// Usar verificación de cache primero para rendimiento
if (_cache.Exists($"product:{id}"))
return true;
// Fallback al repositorio
var exists = _repository.Exists(id);
if (exists)
{
// Cachear el producto para uso futuro
var product = _repository.GetById(id);
if (product != null)
{
_cache.Set($"product:{id}", product);
}
}
return exists;
}
public decimal GetProductPrice(int id)
{
// Ejemplo de cache específico para un campo
return _cache.GetOrSet($"product:price:{id}", () =>
{
var product = _repository.GetById(id);
return product?.Price ?? 0;
}, TimeSpan.FromMinutes(30));
}
}
Servicio de Cache Genérico
public class CacheService : ICacheService
{
private readonly IMemoryCacheManager _cache;
private readonly ILogger<CacheService> _logger;
public CacheService(IMemoryCacheManager cache, ILogger<CacheService> logger)
{
_cache = cache;
_logger = logger;
}
public T? GetOrCreate<T>(string key, Func<T> factory, TimeSpan? expiration = null)
{
return _cache.GetOrSet(key, factory, expiration);
}
public async Task<T?> GetOrCreateAsync<T>(string key, Func<Task<T>> factory, TimeSpan? expiration = null)
{
// Para operaciones asíncronas, verificamos cache primero
var cached = _cache.Get<T>(key);
if (cached != null)
return cached;
// Ejecutar factory asíncrono
var result = await factory();
// Cachear resultado
if (result != null)
{
_cache.Set(key, result, expiration);
}
return result;
}
public void InvalidatePattern(string pattern)
{
// Para patrones como "user:*", "product:category:*"
// Esta implementación requiere tracking de keys (no incluido en la implementación base)
_logger.LogInformation("Pattern invalidation requested: {Pattern}", pattern);
// Implementación simple: remover keys conocidas que coincidan
// En una implementación completa, necesitarías tracking de keys
}
public Dictionary<string, object> GetCacheInfo()
{
return new Dictionary<string, object>
{
{ "CacheEnabled", true },
{ "Timestamp", DateTime.UtcNow }
};
}
}
public interface ICacheService
{
T? GetOrCreate<T>(string key, Func<T> factory, TimeSpan? expiration = null);
Task<T?> GetOrCreateAsync<T>(string key, Func<Task<T>> factory, TimeSpan? expiration = null);
void InvalidatePattern(string pattern);
Dictionary<string, object> GetCacheInfo();
}
Entendiendo la Expiración Deslizante
La Expiración Deslizante reinicia el temporizador de expiración cada vez que se accede a un elemento:
Ejemplo: SlidingExpiration = 10 minutos
14:00 - Elemento almacenado → Expira a las 14:10
14:05 - Elemento accedido → Expira a las 14:15 (¡se reinicia!)
14:12 - Elemento accedido de nuevo → Expira a las 14:22 (¡se reinicia!)
[Sin acceso por 10 minutos] → El elemento expira
Casos de Uso:
✓ Sesiones de usuario (mantener usuarios activos en cache)
✓ Datos de referencia frecuentemente accedidos
✓ Limpieza inteligente (remueve elementos no usados automáticamente)
Métodos Alternativos de Registro
Configuración mediante Delegate
// Configurar opciones programáticamente
builder.Services.AddMemoryCacheService(options =>
{
options.Enabled = true;
options.DefaultExpirationMinutes = 45;
options.DefaultSlidingExpirationMinutes = 10;
options.SizeLimit = 5000;
options.CompactionPercentage = 0.30;
});
Configuración desde Sección Específica
// Usar un nombre de sección de configuración diferente
var cacheSection = builder.Configuration.GetSection("CustomCacheSettings");
builder.Services.AddMemoryCacheService(cacheSection);
Manejo de Errores
La librería maneja errores de forma elegante:
- Cache Deshabilitado: Cuando
Enabled = false
, todas las operaciones son no-ops - Operaciones Get: Retornan null si el cache no está disponible
- Operaciones Set: Loguean errores pero no lanzan excepciones
- Validación: La configuración es validada al inicio
// Las operaciones de cache son seguras incluso si el cache falla
var user = _cache.Get<User>("user:123"); // Retorna null si hay error
_cache.Set("key", value); // Loguea error pero continúa la ejecución
Mejores Prácticas
1. Usar Claves Significativas
Crear claves jerárquicas y descriptivas:
✓ "user:profile:123"
✓ "product:details:456"
✓ "category:products:electronics"
✗ "data123"
✗ "temp_stuff"
2. Manejar Valores Null
Siempre verificar null al obtener del cache:
var user = _cache.Get<User>("user:123");
if (user == null)
{
// Manejar cache miss
user = LoadUserFromDatabase(123);
_cache.Set("user:123", user);
}
3. Usar Patrón GetOrSet
Más eficiente para escenarios de cache-or-create:
var data = _cache.GetOrSet("expensive:data", () => LoadExpensiveData());
4. Considerar Tiempos de Expiración
Balance entre rendimiento y frescura de datos:
// Datos de referencia - expiración más larga
_cache.Set("countries", countries, TimeSpan.FromHours(24));
// Datos de usuario - expiración más corta
_cache.Set("user:123", user, TimeSpan.FromMinutes(15));
// Datos temporales - expiración muy corta
_cache.Set("temp:token", token, TimeSpan.FromMinutes(5));
5. Invalidación de Cache
Remover cache relacionado cuando los datos cambian:
public void UpdateUser(User user)
{
_repository.Update(user);
// Remover cache directo
_cache.Remove($"user:{user.Id}");
// Remover caches relacionados si es necesario
_cache.Remove($"users:department:{user.DepartmentId}");
_cache.Remove("users:active");
}
6. Cache de Listas y Colecciones
public List<Product> GetProductsByCategory(int categoryId)
{
return _cache.GetOrSet($"products:category:{categoryId}", () =>
{
return _repository.GetProductsByCategory(categoryId);
}, TimeSpan.FromMinutes(10));
}
// Invalidar cuando se agregue/modifique un producto
public void AddProduct(Product product)
{
_repository.Add(product);
// Invalidar lista de la categoría
_cache.Remove($"products:category:{product.CategoryId}");
}
Solución de Problemas
Problemas Comunes
El cache no funciona: Verificar Enabled = true
en configuración
Problemas de memoria: Configurar SizeLimit
y CompactionPercentage
apropiados
Errores de configuración: Revisar mensajes de validación en los logs
Problemas de rendimiento: Monitorear ratios de hit/miss del cache
Logging
Habilitar logging de debug para solucionar comportamiento del cache:
{
"Logging": {
"LogLevel": {
"EV.MemoryCache": "Debug"
}
}
}
Validación de Configuración
// Verificar configuración al inicio
public static void ValidateCache(IServiceProvider services)
{
using var scope = services.CreateScope();
var cache = scope.ServiceProvider.GetRequiredService<IMemoryCacheManager>();
// Prueba básica
cache.Set("test", "value", TimeSpan.FromMinutes(1));
var retrieved = cache.Get<string>("test");
if (retrieved == "value")
Console.WriteLine("✅ Cache working correctly");
else
Console.WriteLine("❌ Cache configuration issue");
cache.Remove("test");
}
Requisitos
- .NET 8.0 o superior
- Microsoft.Extensions.Caching.Memory 8.0+
- Microsoft.Extensions.Configuration 8.0+
- Microsoft.Extensions.DependencyInjection 8.0+
- Microsoft.Extensions.Logging 8.0+
- Microsoft.Extensions.Options 8.0+
Licencia
Copyright © 2025 EVI. Todos los derechos reservados.
Cache en Memoria Simple, Rápido y Confiable para .NET 8
Product | Versions Compatible and additional computed target framework versions. |
---|---|
.NET | net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 was computed. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 was computed. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net8.0
- Microsoft.Extensions.Caching.Memory (>= 8.0.1)
- Microsoft.Extensions.Configuration (>= 8.0.0)
- Microsoft.Extensions.Configuration.Abstractions (>= 8.0.0)
- Microsoft.Extensions.Configuration.Binder (>= 8.0.2)
- Microsoft.Extensions.DependencyInjection (>= 8.0.1)
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 8.0.2)
- Microsoft.Extensions.Logging.Abstractions (>= 8.0.2)
- Microsoft.Extensions.Options.ConfigurationExtensions (>= 8.0.0)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
Version | Downloads | Last Updated |
---|---|---|
1.0.0 | 195 | 8/27/2025 |
🎉 Lanzamiento Inicial 1.0.0
✨ Características de Memoria Caché:
• Sistema de caché en memoria de alto rendimiento
• Gestión automática de expiración temporal y absoluta
• Soporte para serialización automática de objetos complejos
• Decoradores para logging de operaciones de caché
• Invalidación inteligente por patrones y tags
• Configuración flexible a través de appsettings.json
• Soporte para cache particionado y distribuído