ASP.NET Core中如何在Scoped类中缓存并使用数据库数据
问题场景
需要缓存Category表的数据并在用户请求时返回,最初实现了如下缓存服务:
public interface ICategoryCache { IEnumerable<Category> GetCategories(); } public class CategoryCache : ICategoryCache { private IEnumerable<Category> _categories; public CategoryCache(ItBuildsDbContext context) { _categories = context.Category.ToList(); } public IEnumerable<Category> GetCategories() { return _categories; } }
尝试将ICategoryCache注册为Singleton时,因依赖的ItBuildsDbContext是Scoped服务,且使用缓存的类为Scoped(无法改为Singleton),触发错误:Cannot consume scoped service。自己实现了一个工厂方案,询问是否有更优解。
当前实现方案
public class CategoryCache { private readonly IEnumerable<Category> _categories; private static CategoryCache? _categoryCache; private CategoryCache(ItBuildsDbContext context) { _categories = context.Category.ToList(); } public static CategoryCache Create(ItBuildsDbContext context) { if(_categoryCache == null) { _categoryCache = new CategoryCache(context); } return _categoryCache; } public IEnumerable<Category> GetCategories() { return _categories!; } } public interface IFactoryCategoryCache { CategoryCache Create(); } public class FactoryCategoryCache : IFactoryCategoryCache { private readonly ItBuildsDbContext _context; public FactoryCategoryCache(ItBuildsDbContext context) { _context = context; } public CategoryCache Create() { return CategoryCache.Create(_context); } } // 注册代码 service.AddScoped<IFactoryCategoryCache, FactoryCategoryCache>();
更优解决方案
方案1:利用DI容器的工厂委托创建Singleton缓存
无需手动管理静态单例,通过DI容器的工厂委托,在创建Singleton缓存时临时创建作用域获取Scoped的DbContext:
public interface ICategoryCache { IEnumerable<Category> GetCategories(); } public class CategoryCache : ICategoryCache { private readonly IEnumerable<Category> _categories; // 直接接收缓存数据,避免构造函数依赖Scoped服务 public CategoryCache(IEnumerable<Category> categories) { _categories = categories; } public IEnumerable<Category> GetCategories() => _categories; }
注册时使用工厂委托:
services.AddSingleton<ICategoryCache>(sp => { // 创建临时作用域,获取Scoped的DbContext using var scope = sp.CreateScope(); var dbContext = scope.ServiceProvider.GetRequiredService<ItBuildsDbContext>(); var categories = dbContext.Category.ToList(); return new CategoryCache(categories); });
优势:
- 由DI容器管理Singleton生命周期,无需手动实现单例逻辑
- 仅在第一次初始化缓存时创建临时作用域,避免Scoped服务与Singleton的生命周期冲突
- 天然线程安全,DI容器的Singleton创建逻辑本身是线程安全的
方案2:结合IMemoryCache实现可配置的缓存
如果需要支持缓存过期、自动刷新等特性,可以使用IMemoryCache封装缓存逻辑:
public interface ICategoryCache { IEnumerable<Category> GetCategories(); } public class CategoryCache : ICategoryCache { private readonly IMemoryCache _memoryCache; private readonly IServiceScopeFactory _scopeFactory; private const string CacheKey = "CategoryList"; public CategoryCache(IMemoryCache memoryCache, IServiceScopeFactory scopeFactory) { _memoryCache = memoryCache; _scopeFactory = scopeFactory; } public IEnumerable<Category> GetCategories() { // GetOrCreate会在缓存不存在时执行初始化逻辑 return _memoryCache.GetOrCreate(CacheKey, entry => { // 可选:设置缓存过期时间,比如1小时自动刷新 entry.AbsoluteExpirationRelativeToNow = TimeSpan.FromHours(1); using var scope = _scopeFactory.CreateScope(); var dbContext = scope.ServiceProvider.GetRequiredService<ItBuildsDbContext>(); return dbContext.Category.ToList(); }); } }
注册代码:
services.AddMemoryCache(); // 注册内存缓存服务 services.AddSingleton<ICategoryCache, CategoryCache>();
优势:
- 支持灵活的缓存过期策略
- 无需手动管理单例,DI容器负责生命周期
- 缓存逻辑更通用,可扩展支持缓存依赖、滑动过期等特性
方案对比
你当前的实现存在线程安全隐患:静态Create方法未加锁,多线程并发调用时可能创建多个CategoryCache实例。上述两种方案均解决了该问题,且更符合.NET依赖注入的设计原则,代码更简洁易维护。
内容的提问来源于stack exchange,提问作者Silny ToJa
相关产品推荐
相关产品推荐

