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

.NET单例服务手动创建作用域使用DbContext触发ObjectDisposedException

单例服务中使用DbContext触发ObjectDisposedException的问题排查与解决

说明:我花了一小时反复检查后找到了解决方案,网上没搜到相关内容,所以发出来帮其他人避坑。
如果对我的架构有意见欢迎指出,我总觉得不该碰到这个问题,但说不出具体原因。


在我的.NET 6应用里,有个要访问数据库的服务,得用DbContext。这个服务注册成了单例,但DbContext按规范没注册成单例。这类问题常见的解决办法是注入IServiceScopeFactory,为每个操作创建并释放作用域。

我严格照着做,却触发了ObjectDisposedException,显然这个方案在我的场景里不管用。问题出在哪?

这个服务是应用的数据库网关,注册成单例是因为要触发事件让后台服务处理特定操作。

我把上下文和作用域的处理逻辑抽成了抽象类,精简后代码如下:
(所有类都重命名了,不是真的做猫相关应用,可能有遗漏的重命名😁)

public abstract class RepositoryBase
{
  private readonly IServiceScopeFactory serviceScopeFactory;

  protected RepositoryBase(IServiceScopeFactory serviceScopeFactory)
  {
    this.serviceScopeFactory = serviceScopeFactory;
  }

  protected void ExecuteInContext(Action<IAnimalContext> function)
  {
    using var scope = serviceScopeFactory.CreateScope();
    function(scope.ServiceProvider.GetRequiredService<IAnimalContext>());
  }

  protected T ExecuteInContext<T>(Func<IAnimalContext, T> function)
  {
    using var scope = serviceScopeFactory.CreateScope();
    return function(scope.ServiceProvider.GetRequiredService<IAnimalContext>());
  }
}

抽象类的使用示例:

internal class CatRepository : RepositoryBase, ICatRepository
{
  public CatRepository(IServiceScopeFactory serviceScopeFactory) : base(serviceScopeFactory)
  { }

  public event Action<Guid>? CatRenamed;

  public IEnumerable<Cat> AllCats => ExecuteInContext(context => context.Cats);

  public void RenameCat(Guid id, string name) => ExecuteInContext(context => {    
    var cat = context.Cats.FindOrThrow(id);
    cat.Name = name;
    context.SaveChanges();
    CatRenamed?.Invoke(cat.id);
  });
}

如果去掉ExecuteInContext里的using语句,程序能正常运行,但每次调用仓储操作都会创建新作用域且从不释放,会导致资源泄漏。

我在ExecuteInContext里加了断点和日志,确认作用域释放前操作已经执行完了。

调用CatRepository.AllCats时的异常堆栈跟踪如下:

fail: Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware[1]
      An unhandled exception has occurred while executing the request.
      System.ObjectDisposedException: Cannot access a disposed context instance. A common cause of this error is disposing a context instance that was resolved from dependency injection and then later trying
 to use the same context instance elsewhere in your application. This may occur if you are calling 'Dispose' on the context instance, or wrapping it in a using statement. If you are using dependency injectio
n, you should let the dependency injection container take care of disposing context instances.
      Object name: 'LogisticsStateManagementContext'.
         at Microsoft.EntityFrameworkCore.DbContext.CheckDisposed()
         at Microsoft.EntityFrameworkCore.DbContext.get_ContextServices()
         at Microsoft.EntityFrameworkCore.DbContext.get_InternalServiceProvider()
         at Microsoft.EntityFrameworkCore.DbContext.get_ChangeTracker()
         at Microsoft.EntityFrameworkCore.Query.CompiledQueryCacheKeyGenerator.GenerateCacheKeyCore(Expression query, Boolean async)
         at Microsoft.EntityFrameworkCore.Query.RelationalCompiledQueryCacheKeyGenerator.GenerateCacheKeyCore(Expression query, Boolean async)
         at Microsoft.EntityFrameworkCore.SqlServer.Query.Internal.SqlServerCompiledQueryCacheKeyGenerator.GenerateCacheKey(Expression query, Boolean async)
         at Microsoft.EntityFrameworkCore.Query.Internal.QueryCompiler.Execute[TResult](Expression query)
         at Microsoft.EntityFrameworkCore.Query.Internal.EntityQueryProvider.Execute[TResult](Expression expression)
         at Microsoft.EntityFrameworkCore.Query.Internal.EntityQueryable`1.GetEnumerator()
         at Microsoft.EntityFrameworkCore.EntityFrameworkQueryableExtensions.IncludableQueryable`2.GetEnumerator()
         at System.Linq.Enumerable.SelectEnumerableIterator`2.MoveNext()
         at System.Text.Json.Serialization.Converters.IEnumerableDefaultConverter`2.OnWriteResume(Utf8JsonWriter writer, TCollection value, JsonSerializerOptions options, WriteStack& state)
         at System.Text.Json.Serialization.JsonCollectionConverter`2.OnTryWrite(Utf8JsonWriter writer, TCollection value, JsonSerializerOptions options, WriteStack& state)
         at System.Text.Json.Serialization.JsonConverter`1.TryWrite(Utf8JsonWriter writer, T& value, JsonSerializerOptions options, WriteStack& state)
         at System.Text.Json.Serialization.JsonConverter`1.WriteCore(Utf8JsonWriter writer, T& value, JsonSerializerOptions options, WriteStack& state)
         at System.Text.Json.Serialization.JsonConverter`1.WriteCoreAsObject(Utf8JsonWriter writer, Object value, JsonSerializerOptions options, WriteStack& state)
         at System.Text.Json.JsonSerializer.WriteCore[TValue](JsonConverter jsonConverter, Utf8JsonWriter writer, TValue& value, JsonSerializerOptions options, WriteStack& state)
         at System.Text.Json.JsonSerializer.WriteStreamAsync[TValue](Stream utf8Json, TValue value, JsonTypeInfo jsonTypeInfo, CancellationToken cancellationToken)
         at System.Text.Json.JsonSerializer.WriteStreamAsync[TValue](Stream utf8Json, TValue value, JsonTypeInfo jsonTypeInfo, CancellationToken cancellationToken)
         at System.Text.Json.JsonSerializer.WriteStreamAsync[TValue](Stream utf8Json, TValue value, JsonTypeInfo jsonTypeInfo, CancellationToken cancellationToken)
         at Microsoft.AspNetCore.Mvc.Formatters.SystemTextJsonOutputFormatter.WriteResponseBodyAsync(OutputFormatterWriteContext context, Encoding selectedEncoding)
         at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeNextResultFilterAsync>g__Awaited|30_0[TFilter,TFilterAsync](ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object
state, Boolean isCompleted)
         at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Rethrow(ResultExecutedContextSealed context)
         at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.ResultNext[TFilter,TFilterAsync](State& next, Scope& scope, Object& state, Boolean& isCompleted)
         at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.InvokeResultFilters()
      --- End of stack trace from previous location ---
         at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeFilterPipelineAsync>g__Awaited|20_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isComplet
ed)
         at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeAsync>g__Logged|17_1(ResourceInvoker invoker)
         at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeAsync>g__Logged|17_1(ResourceInvoker invoker)
         at Microsoft.AspNetCore.Routing.EndpointMiddleware.<Invoke>g__AwaitRequestTask|6_0(Endpoint endpoint, Task requestTask, ILogger logger)
         at Microsoft.AspNetCore.Authorization.Policy.AuthorizationMiddlewareResultHandler.HandleAsync(RequestDelegate next, HttpContext context, AuthorizationPolicy policy, PolicyAuthorizationResult authori
zeResult)
         at Microsoft.AspNetCore.Authorization.AuthorizationMiddleware.Invoke(HttpContext context)
         at Microsoft.AspNetCore.Authentication.AuthenticationMiddleware.Invoke(HttpContext context)
         at Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware.Invoke(HttpContext context)

解决方案

从堆栈跟踪里能看出问题根源:AllCats属性返回的是IEnumerable<Cat>,也就是EF Core的DbSet<Cat>,它是延迟执行的!

当ExecuteInContext执行完毕,作用域被释放,DbContext也随之销毁。但ASP.NET Core在返回JSON时才会去枚举这个集合,此时DbContext已经被释放,自然触发ObjectDisposedException。

解决方法很简单:把延迟执行的查询结果转为立即执行的集合,比如用ToList():

public IEnumerable<Cat> AllCats => ExecuteInContext(context => context.Cats.ToList());

这样在ExecuteInContext内部就完成了数据库查询和枚举,DbContext还处于存活状态。后续的JSON序列化操作使用的是内存中的List,不会再访问已释放的DbContext。

另外要注意排查所有类似的延迟执行场景,确保所有需要访问DbContext的操作都在作用域和DbContext的生命周期内完成。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.21 00:57:22