ASP.NET Core EF Core访问已释放DatabaseContext异常排查
异常产生原因
- EF Core默认通过
AddDbContext注册的DatabaseContext生命周期是Scoped(请求作用域),即每个HTTP请求会创建一个实例,请求结束后实例会被DI容器释放。 - 你将
IDatabaseChangeNotificationService注册为Scoped生命周期,却在应用启动的Startup.Configure方法中直接解析该服务实例并调用Config()注册数据库变更回调。Configure方法执行完成后,这次解析对应的临时作用域就会被销毁,服务实例中持有的_DBContext、_userManager都是该作用域内的实例,会随作用域一同被释放。 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
相关产品推荐
相关产品推荐

