c# - 在 API 中抽象出对 IMemoryCache 的异步调用
问题描述
我一直在使用类似于示例 1 的操作来为我的 .NET Core API 异步缓存 json 结果。MemoryCache
是一个实例IMemoryCache
。
示例 1(按预期工作):
[HttpGet]
public async Task<IActionResult> MyAction() =>
Json(await MemoryCache.GetOrCreateAsync(
"MyController_MyAction",
entry => myService.GetAllAsync()
));
我的许多动作中都重复了对Json()
和的调用。MemoryCache.GetOrCreate()
在我的真实应用中,还有更多重复的实现细节,例如设置AbsoluteExpirationRelativeToNow
值和返回NotFound()
空值。我想将所有这些抽象为一个共享方法,以便每个操作仅将其独特的细节传递给共享方法的调用。
为此,我为我的操作中的两个变量中的每一个提取了一个变量。例如:
示例 2(缓存既不更新也不检索):
[HttpGet]
public async Task<IActionResult> MyAction()
{
var task = myService.GetAllAsync();
const string cacheKey = "MyController_MyAction";
return Json(await MemoryCache.GetOrCreateAsync(cacheKey, entry => task));
}
下一步是提取一个共享方法Get()
,例如:
示例 3(由于示例 2 不起作用,因此不起作用):
[HttpGet]
public async Task<IActionResult> MyAction()
{
var task = myService.GetAllAsync();
const string cacheKey = "MyController_MyAction";
return await Get(task, cacheKey);
}
protected async Task<IActionResult> Get(Task<T> task, string cacheKey =>
return Json(await MemoryCache.GetOrCreateAsync(cacheKey, entry => task));
示例 1 成功地从缓存中检索到后续结果。但是,示例 2null
会在后续请求的缓存中找到并每次重新检索数据(通过临时调试TryGetValue()
语句以及监视访问我的数据库的基础 SQL 查询进行验证)。
对我来说,示例 1 和示例 2 应该是相同的。但是,也许我对 async/await 和 Tasks 的理解不够(很可能)。
如何从我的操作中抽象出重复的实现细节(例如Json()
和调用),同时仍然以异步方式MemoryCache.GetOrCreate()
成功地更新和检索?IMemoryCache
解决方案
var task = myService.GetAllAsync();
这将已经运行该GetAllAsync
方法,因此通过这样做,您可以防止内存缓存的惰性行为,它只会在缓存键不可用时调用该方法。
为了继续这样做,您必须存储一个创建值的实际表达式,因此您必须这样做:
Func<MyObject> createValue = () => myService.GetAllAsync();
const string cacheKey = "MyController_MyAction";
return Json(await MemoryCache.GetOrCreateAsync(cacheKey, entry => createValue()));
所以,把它抽象出来,这就是你最终可能得到的结果:
public Task<IActionResult> MyAction()
=> GetCache("MyController_MyAction", () => myService.GetAllAsync());
该方法将像这样实现:
private async Task<IActionResult> GetCache<T>(string cacheKey, Func<Task<T>> createAction)
{
var result = await MemoryCache.GetOrCreateAsync(cacheKey, entry => createAction());
return Json(result);
}
如果缓存键始终为<ControllerName>_<ActionName>
,您甚至可以再走一步,并使用以下命令从调用中自动推断CallerMemberNameAttribute
:
private async Task<IActionResult> GetCache<T>(Func<Task<T>> createAction, [CallerMemberName] string actionName = null)
{
var cacheKey = GetType().Name + "_" + actionName;
var result = await MemoryCache.GetOrCreateAsync(cacheKey, entry => createAction());
return Json(result);
}
所以你可以像这样使用它:
public Task<IActionResult> MyAction()
=> GetCache(() => myService.GetAllAsync());
推荐阅读
- android - 在Android中使用列表与for循环插入房间
- sql - 在 postgresql 中自动生成字母数字 (AANNNN) id
- javascript - 在 Angular 版本 11 中找不到延迟模块
- r - R如何合并2个列表
- javascript - 当输入框字符串长于框大小时,Vue Sortable + Draggable 不起作用
- python - 如何将 url 作为 slug 字段发送到 Django 中的 url
- python - 为什么我们需要将测试函数包装在一个类中?
- ruby-on-rails - Rails `create` 验证要求
- sql - 如何在服务器上连接我的网站和数据库
- javascript - 如何在现有网站中实现 Node.js 聊天应用程序?