.NET Core API单请求内无Session临时存储对象列表的最佳实践
请求周期内复用数据的无Session方案(.NET Core + AWS EKS)
针对你单个请求内大量重复SQL调用的问题,以下是不需要Session的可行方案和最佳实践:
1. 请求级Scoped服务缓存
自定义一个简单的缓存类,比如RequestScopedCache,内部用ConcurrentDictionary<string, object>存储数据,然后在Program.cs里将其注册为Scoped生命周期:
services.AddScoped<RequestScopedCache>();
各个模块需要数据时,先通过这个Scoped实例查询缓存键(可通过SQL语句+参数的组合生成唯一键,比如$"Product_{productId}"),如果未找到则执行SQL查询,将结果存入缓存。由于Scoped服务在单个请求内是唯一实例,请求结束后会被自动回收,既不会跨请求污染数据,也不会像静态类那样占用容器全局内存。
2. 直接使用HttpContext.Items
HttpContext.Items是.NET Core内置的请求级键值对存储,天然绑定当前请求,请求完成后自动释放资源。用法简单直接:
var cacheKey = $"User_{userId}"; if (!HttpContext.Items.ContainsKey(cacheKey)) { var user = await _dbContext.Users.FindAsync(userId); HttpContext.Items[cacheKey] = user; } var cachedUser = (User)HttpContext.Items[cacheKey];
这种方式无需额外定义类,适合简单场景,唯一需要注意的是要统一规范缓存键,避免键冲突。
3. 封装通用查询缓存方法
在Repository或基础服务层封装一个通用的GetOrFetchAsync方法,统一处理请求内的缓存逻辑,避免业务代码重复:
public async Task<T> GetOrFetchAsync<T>(string cacheKey, Func<Task<T>> fetchFunc) { if (_requestCache.TryGetValue(cacheKey, out var cachedValue)) { return (T)cachedValue; } var result = await fetchFunc(); _requestCache.TryAdd(cacheKey, result); return result; }
业务模块只需调用该方法,传入缓存键和查询逻辑即可,无需关心缓存细节,代码更简洁易维护。
4. 从根源减少重复查询
上述方法是“治标”,更关键的是优化SQL本身,从根源减少重复调用:
- 将循环内的单条查询改为批量查询:比如原本循环100次查询单条用户数据,改成
SELECT * FROM Users WHERE Id IN (@ids)一次性拉取所有需要的数据,之后在内存中按需取用,直接将数据库调用从100次降至1次。 - 排查N+1问题:如果使用EF Core,检查是否存在未提前关联的查询,用
Include/ThenInclude或者AsSplitQuery一次性加载关联数据,避免后续循环中反复查询关联表。
5. EKS环境下的注意事项
- 容器化部署中,请求级缓存是完全隔离的,每个请求的缓存数据不会共享,请求结束后资源自动释放,不会增加容器的长期负载,无需担心内存泄漏问题。
- 绝对不要用静态类或Singleton服务存储请求级数据,这会导致多请求间的数据污染,还会长期占用容器内存,升高负载。
内容的提问来源于stack exchange,提问作者Subrata
相关产品推荐
相关产品推荐

