如何在单例类中访问DbContext?数据库数据缓存实现咨询
问题分析与解决方案
一、现有代码的核心问题
- 单例类里的
_context完全没注入,直接用肯定会报空引用异常 - 静态集合
extAll的合并逻辑写错了,两次Union的写法会生成多余的中间列表,应该用链式调用一次性合并 - 手动实现单例容易踩线程安全的坑,不如直接用ASP.NET Core自带的DI容器管理更可靠
二、单例类访问DbContext的正确方式
推荐方案:用DI容器注册单例并注入DbContext
抛弃手动写的单例逻辑,让框架帮你管理单例生命周期,同时自动注入DbContext:
修改ExternalDatabase类:
public sealed class ExternalDatabase { private readonly List<EXT_PramexInserts> _pramexInserts; private readonly List<EXT_PrototypeEndmills> _prototypeEndmills; private readonly List<IProducts> _extAll; // 构造函数直接注入DbContext public ExternalDatabase(Context context) { _pramexInserts = context.Set<EXT_PramexInserts>().ToList(); _prototypeEndmills = context.Set<EXT_PrototypeEndmills>().ToList(); // 一次性合并两个集合,避免多余操作 _extAll = _pramexInserts.Cast<IProducts>() .Union(_prototypeEndmills.Cast<IProducts>()) .ToList(); } public List<IProducts> GetExtraData(string materialId) { return _extAll.FindAll(p => p.MaterialID == materialId); } }
在Program.cs里注册服务:
builder.Services.AddDbContext<Context>(options => options.UseSqlServer(builder.Configuration.GetConnectionString("YourConnString"))); // 注册为单例,应用启动时自动初始化并注入DbContext builder.Services.AddSingleton<ExternalDatabase>();
控制器里直接注入使用:
[ApiController] [Route("DB")] public class EXT_TablesController : ControllerBase { private readonly ExternalDatabase _externalDb; // 直接注入单例实例,不用自己手动拿Instance public EXT_TablesController(ExternalDatabase externalDb) { _externalDb = externalDb; } [HttpPost] public IEnumerable<IProducts> Post([FromBody] EXT_Request request) { var (MaterialID, Type, Brand) = request; if (Type != null && Brand != null) { string selectedTable = $"EXT_{Brand}_{Type}"; switch (selectedTable) { case "EXT_PRAMEX_INSERTS": return _externalDb.GetExtraData(MaterialID); default: return Enumerable.Empty<IProducts>(); } } return Enumerable.Empty<IProducts>(); } }
备选方案:手动初始化单例时传入DbContext(不推荐)
如果非要自己写单例逻辑,那得在应用启动时手动传入DbContext实例:
修改ExternalDatabase类:
public sealed class ExternalDatabase { private static ExternalDatabase _instance = null; private static readonly object _instanceLock = new object(); private readonly List<EXT_PramexInserts> _pramexInserts; private readonly List<EXT_PrototypeEndmills> _prototypeEndmills; private readonly List<IProducts> _extAll; // 私有构造函数,只允许通过Initialize方法创建实例 private ExternalDatabase(Context context) { _pramexInserts = context.Set<EXT_PramexInserts>().ToList(); _prototypeEndmills = context.Set<EXT_PrototypeEndmills>().ToList(); _extAll = _pramexInserts.Cast<IProducts>() .Union(_prototypeEndmills.Cast<IProducts>()) .ToList(); } public static ExternalDatabase Instance { get { lock (_instanceLock) { if (_instance == null) { throw new InvalidOperationException("请先调用Initialize方法初始化ExternalDatabase"); } return _instance; } } } // 提供初始化入口,传入DbContext public static void Initialize(Context context) { lock (_instanceLock) { if (_instance == null) { _instance = new ExternalDatabase(context); } } } public List<IProducts> GetExtraData(string materialId) { return _extAll.FindAll(p => p.MaterialID == materialId); } }
在Program.cs启动时初始化:
var app = builder.Build(); // 创建作用域获取DbContext实例,初始化单例 using (var scope = app.Services.CreateScope()) { var context = scope.ServiceProvider.GetRequiredService<Context>(); ExternalDatabase.Initialize(context); } // 后续中间件配置... app.Run();
三、更优实现建议
- 优先用DI容器管理单例:手动写单例容易出线程安全问题,框架自带的DI已经帮你处理好了,省心又可靠
- 加个数据刷新机制:哪怕数据库变更少,万一需要手动刷新数据,可以给
ExternalDatabase加个刷新方法:
public void Refresh(Context context) { _pramexInserts.Clear(); _pramexInserts.AddRange(context.Set<EXT_PramexInserts>().ToList()); _prototypeEndmills.Clear(); _prototypeEndmills.AddRange(context.Set<EXT_PrototypeEndmills>().ToList()); _extAll.Clear(); _extAll.AddRange(_pramexInserts.Cast<IProducts>().Union(_prototypeEndmills.Cast<IProducts>())); }
然后可以加个接口比如[HttpPost("refresh")]来触发刷新
3. 用只读集合提高安全性:把内部集合改成只读,避免外部代码不小心修改内存数据:
private readonly IReadOnlyList<IProducts> _extAll; // 构造函数里初始化 _extAll = _pramexInserts.Cast<IProducts>() .Union(_prototypeEndmills.Cast<IProducts>()) .ToList() .AsReadOnly(); // 查询方法返回只读集合 public IReadOnlyList<IProducts> GetExtraData(string materialId) { return _extAll.Where(p => p.MaterialID == materialId).ToList().AsReadOnly(); }
- 别返回null:控制器里返回
Enumerable.Empty<IProducts>()代替null,符合接口规范,避免客户端处理空引用报错
内容的提问来源于stack exchange,提问作者Burton
相关产品推荐
相关产品推荐

