You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在单例类中访问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();

三、更优实现建议

  1. 优先用DI容器管理单例:手动写单例容易出线程安全问题,框架自带的DI已经帮你处理好了,省心又可靠
  2. 加个数据刷新机制:哪怕数据库变更少,万一需要手动刷新数据,可以给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();
}
  1. 别返回null:控制器里返回Enumerable.Empty<IProducts>()代替null,符合接口规范,避免客户端处理空引用报错

内容的提问来源于stack exchange,提问作者Burton

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.16 10:50:36