Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Caching

In-Memory Cache

IMemoryCache

// Đăng ký
builder.Services.AddMemoryCache();

// Sử dụng
public class ProductService
{
    private readonly IMemoryCache _cache;
    private readonly AppDbContext _context;
    
    public ProductService(IMemoryCache cache, AppDbContext context)
    {
        _cache = cache;
        _context = context;
    }
    
    public async Task<List<Product>> GetProductsAsync()
    {
        // TryGetValue - Kiểm tra cache
        if (!_cache.TryGetValue("products", out List<Product> products))
        {
            // Load từ database
            products = await _context.Products.ToListAsync();
            
            // Set cache với options
            var options = new MemoryCacheEntryOptions()
                .SetSlidingExpiration(TimeSpan.FromMinutes(10))
                .SetAbsoluteExpiration(TimeSpan.FromHours(1))
                .SetPriority(CacheItemPriority.Normal)
                .SetSize(products.Count);
            
            _cache.Set("products", products, options);
        }
        
        return products;
    }
    
    public async Task<Product> GetProductByIdAsync(int id)
    {
        var key = $"product_{id}";
        
        return await _cache.GetOrCreateAsync(key, async entry =>
        {
            entry.SlidingExpiration = TimeSpan.FromMinutes(5);
            
            return await _context.Products.FindAsync(id);
        });
    }
}

Cache Options

var options = new MemoryCacheEntryOptions()
    // Thời gian cache không được access trước khi expire
    .SetSlidingExpiration(TimeSpan.FromMinutes(10))
    // Thời gian cache tồn tại tuyệt đối
    .SetAbsoluteExpiration(TimeSpan.FromHours(1))
    // Kết hợp cả hai
    .SetAbsoluteExpirationRelativeToNow(TimeSpan.FromHours(1))
    // Callback khi cache bị remove
    .RegisterPostEvictionCallback((key, value, reason, state) =>
    {
        Console.WriteLine($"Cache '{key}' removed: {reason}");
    });

Distributed Cache (Redis)

Cài đặt

dotnet add package StackExchange.Redis

Cấu hình

builder.Services.AddStackExchangeRedisCache(options =>
{
    options.Configuration = "localhost:6379";
    options.InstanceName = "MyApp:";
});

Sử dụng

public class RedisCacheService
{
    private readonly IDistributedCache _cache;
    
    public RedisCacheService(IDistributedCache cache)
    {
        _cache = cache;
    }
    
    // Set
    public async Task SetAsync<T>(string key, T value)
    {
        var json = JsonSerializer.Serialize(value);
        var bytes = Encoding.UTF8.GetBytes(json);
        
        await _cache.SetAsync(key, bytes, new DistributedCacheEntryOptions
        {
            AbsoluteExpirationRelativeToNow = TimeSpan.FromHours(1),
            SlidingExpiration = TimeSpan.FromMinutes(10)
        });
    }
    
    // Get
    public async Task<T> GetAsync<T>(string key)
    {
        var bytes = await _cache.GetAsync(key);
        if (bytes == null) return default;
        
        var json = Encoding.UTF8.GetString(bytes);
        return JsonSerializer.Deserialize<T>(json);
    }
    
    // Remove
    public async Task RemoveAsync(string key)
    {
        await _cache.RemoveAsync(key);
    }
}

Response Caching

Cấu hình

// Program.cs
builder.Services.AddResponseCaching();

var app = builder.Build();
app.UseResponseCaching();

Sử dụng

[ApiController]
[Route("api/[controller]")]
[ResponseCache(Duration = 60, VaryByHeader = "Accept")]
public class ProductsController : ControllerBase
{
    [HttpGet]
    [ResponseCache(Duration = 120)]
    public IActionResult GetProducts()
    {
        return Ok(new { data = "cached" });
    }
}

Cache Headers

[HttpGet]
public IActionResult GetProducts()
{
    Response.Headers.CacheControl = "public, max-age=60";
    Response.Headers.Vary = "Accept-Encoding";
    
    return Ok();
}

Cache Strategies

Cache-Aside Pattern

┌─────────────────────────────────────────────────────────────────┐
│                      CACHE-ASIDE PATTERN                       │
├─────────────────────────────────────────────────────────────────┤
│                                                                  │
│   1. Request → Cache                                            │
│                         ↓                                       │
│   2. Cache hit? ──No──→ Database → Cache → Response            │
│        ↓                                                        │
│       Yes                                                        │
│        ↓                                                        │
│   3. Response (from cache)                                       │
│                                                                  │
└─────────────────────────────────────────────────────────────────┘
public async Task<Product> GetProductAsync(int id)
{
    var key = $"product_{id}";
    
    // 1. Check cache
    var cached = await _cache.GetAsync<Product>(key);
    if (cached != null)
        return cached;
    
    // 2. Load from database
    var product = await _context.Products.FindAsync(id);
    if (product != null)
    {
        // 3. Store in cache
        await _cache.SetAsync(key, product, TimeSpan.FromMinutes(10));
    }
    
    return product;
}

Invalidate Cache

public async Task UpdateProductAsync(Product product)
{
    // Update database
    _context.Products.Update(product);
    await _context.SaveChangesAsync();
    
    // Invalidate cache
    await _cache.RemoveAsync($"product_{product.Id}");
}