
在应用程序开发的某个阶段,通常是相当早期的时候,你会意识到应用程序很慢。经过一些研究,罪魁祸首是不必要地检索相同的数据,然后灵光一闪,你会想:“我需要一些缓存。”
缓存是一种宝贵的模式,用于消除对数据库或第三方 API 的冗余调用。Microsoft 提供了 IMemoryCache
用于基于时间的缓存,但有时基于时间的缓存并不是你所需要的。在本文中,我们将探讨请求作用域缓存以及它如何使我们受益。
什么是请求缓存?请求缓存是一种在 Web 请求生命周期内缓存数据的机制。在 dot-net 中,我们通过 HttpContext.Items 集合在某种程度上具备了这种能力,但是 HttpContext
并不以其可注入性而闻名。
请求作用域缓存有几个好处:首先,它消除了对过期数据的担忧。在大多数情况下,请求在不到一秒的时间内执行,这通常不足以使数据变得过期。其次,过期不是一个问题,因为数据在请求结束时就会消失。
开箱即用,Asp.Net Core 没有可注入的缓存。如前所述,HttpContext.Items
是一个选择,但它不是一个优雅的解决方案。
幸运的是,ASP.Net Core 通过使用内置的依赖注入(DI)框架为我们提供了创建可注入请求缓存实现的工具。
内置的 DI 框架为依赖项提供了三种生命周期:Singleton
、Scoped
和 Transient
。Singleton
是应用程序的生命周期,Scoped 是请求的生命周期,Transient 是每次请求的新实例。
我创建了一个以 IMemoryCache
接口为模型的接口,以保持一致性。
接口
public interface IRequestCache
{
/// <summary>
/// Add the value into request cache. If the key already exists, the value is overwritten.
/// </summary>
/// <param name="key"></param>
/// <param name="value"></param>
/// <typeparam name="TValue"></typeparam>
void Add<TValue>(string key, TValue value);
/// <summary>
/// Remove the key from the request cache
/// </summary>
/// <param name="key"></param>
void Remove(string key);
/// <summary>
/// Retrieve the value by key, if the key is not in the cache then the add func is called
/// adding the value to cache and returning the added value.
/// </summary>
/// <param name="key"></param>
/// <param name="add"></param>
/// <typeparam name="TValue"></typeparam>
/// <returns></returns>
TValue RetrieveOrAdd<TValue>(string key, Func<TValue> add);
/// <summary>
/// Retrieves the value by key. When the key does not exist the default value for the type is returned.
/// </summary>
/// <param name="key"></param>
/// <typeparam name="TValue"></typeparam>
/// <returns></returns>
TValue Retrieve<TValue>(string key);
}
实现
public class RequestCache : IRequestCache
{
IDictionary<string, object> _cache = new Dictionary<string, object>();
/// <summary>
/// Add the value into request cache. If the key already exists, the value is overwritten.
/// </summary>
/// <param name="key"></param>
/// <param name="value"></param>
/// <typeparam name="TValue"></typeparam>
public void Add<TValue>(string key, TValue value)
{
_cache[key] = value;
}
/// <summary>
/// Remove the key from the request cache
/// </summary>
/// <param name="key"></param>
public void Remove(string key)
{
if (_cache.ContainsKey(key))
{
_cache.Remove(key);
}
}
/// <summary>
/// Retrieve the value by key, if the key is not in the cache then the add func is called
/// adding the value to cache and returning the added value.
/// </summary>
/// <param name="key"></param>
/// <param name="add"></param>
/// <typeparam name="TValue"></typeparam>
/// <returns></returns>
public TValue RetrieveOrAdd<TValue>(string key, Func<TValue> add)
{
if (_cache.ContainsKey(key))
{
return (TValue)_cache[key];
}
var value = add();
_cache[key] = value;
return value;
}
/// <summary>
/// Retrieves the value by key. When the key does not exist the default value for the type is returned.
/// </summary>
/// <param name="key"></param>
/// <typeparam name="TValue"></typeparam>
/// <returns></returns>
public TValue Retrieve<TValue>(string key)
{
if (_cache.ContainsKey(key))
{
return (TValue)_cache[key];
}
return default(TValue);
}
}
使用 ASP.Net Core 的 DI 框架,我们将其配置为 Scoped
。
services.AddScoped<IRequestCache, RequestCache>();
使用方法
public class UserService
{
private readonly IRequestCache _cache;
private readonly IUserRepository _userRepository;
public UserService(IRequestCache cache, IUserRepository userRepository)
{
_cache = cache;
_userRepository = userRepository;
}
public User RetrieveUserById(int userId)
{
var buildCacheKey = UserService.BuildCacheKey(userId);
return _cache.RetrieveOrAdd(BuildCacheKey, () => { return _userRepository.RetrieveUserBy(userId); });
}
public void Delete(int userId)
{
var buildCacheKey = UserService.BuildCacheKey(userId);
_userRepository.Delete(userId);
_cache.Remove(BuildCacheKey(userId));
}
private static string BuildCacheKey(int userId)
{
return $"user_{userId}";
}
}
就是这样!请求缓存现在可以在任何需要的地方注入。
访问 Git 仓库,随时可以试用这些代码。
作者:Chuck Conway 专注于软件工程和生成式人工智能。在社交媒体上与他联系:X (@chuckconway) 或访问他的 YouTube。