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

ASP.NET Core EF Core访问已释放DatabaseContext异常排查

异常产生原因
  1. EF Core默认通过AddDbContext注册的DatabaseContext生命周期是Scoped(请求作用域),即每个HTTP请求会创建一个实例,请求结束后实例会被DI容器释放。
  2. 你将IDatabaseChangeNotificationService注册为Scoped生命周期,却在应用启动的Startup.Configure方法中直接解析该服务实例并调用Config()注册数据库变更回调。Configure方法执行完成后,这次解析对应的临时作用域就会被销毁,服务实例中持有的_DBContext、_userManager都是该作用域内的实例,会随作用域一同被释放。
  3. SqlDependency的OnChange回调是由后台独立线程触发的,执行时机远晚于启动阶段的作用域销毁时间,此时你调用已经被释放的_DBContext实例查询数据,就会抛出对象已释放的异常。
修复方案

按以下步骤调整代码即可解决问题:

  • 第一步:修正服务生命周期。SqlDependencyService是常驻后台运行的通知服务,需要将其注册为单例,避免被DI容器意外释放:
// 修改ConfigureServices中对应注册代码
// 原代码:services.AddScoped<IDatabaseChangeNotificationService, SqlDependencyService>();
services.AddSingleton<IDatabaseChangeNotificationService, SqlDependencyService>();
  • 第二步:不要在单例服务中直接注入、缓存Scoped生命周期的DatabaseContext和UserManager,改为注入IServiceScopeFactory,用于在每次回调触发时创建临时作用域解析服务,用完即释放,符合EF Core的生命周期设计。
  • 第三步:调整TableUsersChanged回调逻辑,每次需要访问数据库时,通过IServiceScopeFactory创建独立作用域,从作用域中获取全新的DatabaseContext和UserManager实例操作,操作完成后自动释放作用域。

调整后的SqlDependencyService核心代码如下:

public class SqlDependencyService : IDatabaseChangeNotificationService
{
    public string FullName;
    public string UserId;
    private readonly IConfiguration configuration;
    private readonly IHubContext<ChatHub> chatHub;
    private readonly IServiceScopeFactory _serviceScopeFactory;
    List<string> AvailableUsers = new List<string>();

    // 构造函数不再直接注入DbContext和UserManager,替换为IServiceScopeFactory
    public SqlDependencyService(IServiceScopeFactory serviceScopeFactory, IConfiguration _configuration, IHubContext<ChatHub> _chatHub)
    {
        _serviceScopeFactory = serviceScopeFactory;
        configuration = _configuration;
        chatHub = _chatHub;
    }

    public void Config()
    {
        TableUsersAvailabilitySensor();
    }

    private void TableUsersAvailabilitySensor()
    {
        string connectionString = configuration.GetConnectionString("DatabaseContextConection");

        using (var conn = new SqlConnection(connectionString))
        {
            if (conn.State != System.Data.ConnectionState.Open)
            {
                conn.Open();
            }

            using (var cmd = new SqlCommand(@"Select IsActive from [dbo].AspNetUsers", conn))
            {
                cmd.Notification = null;
                SqlDependency dependency = new SqlDependency(cmd);
                dependency.OnChange += TableUsersChanged;
                SqlDependency.Start(connectionString);
                cmd.ExecuteReader();
            }
        }
    }

    private void TableUsersChanged(object sender, SqlNotificationEventArgs e)
    {
        if (e.Type == SqlNotificationType.Change)
        {
            string text = checkAvailabilityChanged(e);

            if (text == "Updated" || text == "Inserted")
            {
                // 每次回调创建独立作用域
                using (var scope = _serviceScopeFactory.CreateScope())
                {
                    // 从当前作用域解析需要的服务
                    var dbContext = scope.ServiceProvider.GetRequiredService<DatabaseContext>();
                    var userManager = scope.ServiceProvider.GetRequiredService<UserManager<ApplicationUser>>();

                    var list = dbContext.Users.Where(a => a.IsActive == true).ToList();
                    AvailableUsers.AddRange(list.Select(a => a.UserName));
                    var currentUserId = userManager.GetUserId(AccountController.currentUser);
                    var _currentUser = dbContext.Users.Find(currentUserId);
                    FullName = _currentUser.FirstName + " " + _currentUser.LastName;
                    UserId = currentUserId;
                    // 等待SignalR推送完成后再释放作用域
                    chatHub.Clients.Clients(AvailableUsers).SendAsync("AddMeToYourContacts", FullName, UserId).Wait();
                }
            }
        }

        TableUsersAvailabilitySensor();
    }

    private string checkAvailabilityChanged(SqlNotificationEventArgs e)
    {
        switch (e.Info)
        {
            case SqlNotificationInfo.Update:
                return "Updated";
            case SqlNotificationInfo.Delete:
                return "Deleted";
            case SqlNotificationInfo.Insert:
                return "Inserted";
            default:
                return "Nothing occurred";
        }
    }
}

额外代码优化提示

你当前DatabaseContext中的SaveChanges方法使用new关键字隐藏了基类方法,会导致外部调用基类的同步SaveChanges方法时不会执行你的自定义逻辑,建议改为override重写对应方法,该问题和本次异常无直接关联,但会留下隐患。


内容的提问来源于stack exchange,提问作者Abdulmajeed Naji Alshari

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.28 22:15:48