无法访问用于异步方法的 .NET Core 中 DbContext 的已释放对象 [英] Cannot access a disposed object for DbContext in .NET Core for Async method

查看:24
本文介绍了无法访问用于异步方法的 .NET Core 中 DbContext 的已释放对象的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我的一个微服务 Web api 遇到了一个奇怪的问题.我的异步 GET 方法为我的 DbContext 抛出一个无法访问已处理的对象异常,除非它们第一次被调用.我尝试在网上寻找答案,但没有任何效果.我确保我的方法不是 async void 并且我等待必要的调用.由于我的 POST 和 DELETE 方法工作正常,我相当确定真正的罪魁祸首是 IMapper 实例.我认为它可能总是指向 DbContext 的第一个实例,这就是为什么第一次有效但之后无效的原因.任何帮助或指示将不胜感激

I am having a weird problem in one of my microservice web api. My async GET methods throw a Cannot access a disposed object exception for my DbContext except for the very first time they are invoked. I tried looking online for an answer but nothing worked. I made sure my methods are not async void and I await the necessary calls. Since my POST and DELETE methods work fine, I am fairly certain that the real culprit is the IMapper instance. I think it might always point to the first instance of the DbContext and that is why the works the first time but not the ones after. Any help or pointers would be gladly appreciated

这是代码的一些快照.

Startup.cs

...
// Add AutoMapper
        services.AddAutoMapper(new Assembly[] { typeof(AutoMapperProfile).GetTypeInfo().Assembly });

// Add DbContext using NoSQL Server Provider
services.AddDbContext<ProfileDbContext>(options =>
            options.UseMongoDb(Configuration.GetConnectionString("TeamJobProfilesDatabase")));
...

MyController.cs

MyController.cs

    // GET api/profiles
    [HttpGet]
    [ProducesResponseType(StatusCodes.Status200OK)]
    public async Task<ActionResult<ProfilesListViewModel>> GetAll()
    {
        return Ok(await Mediator.Send(new GetAllProfilesQuery()));
    }

    // GET api/profiles/{id}
    [HttpGet("{id}")]
    [ProducesResponseType(StatusCodes.Status200OK)]
    [ProducesResponseType(StatusCodes.Status404NotFound)]
    public async Task<ActionResult<ProfileViewModel>> Get(int id)
    {
        return Ok(await Mediator.Send(new GetProfileQuery { Id = id }));
    }

GetAllProfilesQueryHandler.cs

GetAllProfilesQueryHandler.cs

public class GetAllProfilesQueryHandler : IRequestHandler<GetAllProfilesQuery, ProfilesListViewModel>
{
    private readonly ProfileDbContext _context;
    private readonly IMapper _mapper;

    public GetAllProfilesQueryHandler(ProfileDbContext context, IMapper mapper)
    {
        _context = context;
        _mapper = mapper;
    }

    public async Task<ProfilesListViewModel> Handle(GetAllProfilesQuery request, CancellationToken cancellationToken)
    {
        return new ProfilesListViewModel
        {
            Profiles = await _context.Profiles.ProjectTo<ProfileLookupModel>(_mapper.ConfigurationProvider).ToListAsync(cancellationToken)
        };
    }
}

ProfileDbContext.cs

ProfileDbContext.cs

[MongoDatabase("profileDb")]
public class ProfileDbContext : DbContext
{
    public ProfileDbContext(DbContextOptions<ProfileDbContext> options)
        : base(options)
    {
    }

    public DbSet<Domain.Entities.Profile> Profiles { get; set; }
}

异常信息

{错误": [无法访问已处置的对象.此错误的常见原因是处置从依赖注入解决的上下文,然后尝试在应用程序的其他地方使用相同的上下文实例.如果您在上下文,或将上下文包装在 using 语句中.如果使用依赖项注入,则应让依赖项注入容器负责处理上下文实例. 对象名称:'ProfileDbContext'."],"stackTrace": " 在 Microsoft.EntityFrameworkCore.DbContext.CheckDisposed() 在 Microsoft.EntityFrameworkCore.DbContext.get_InternalServiceProvider() 在 Microsoft.EntityFrameworkCore.DbContext.get_ChangeTracker() 在 Microsoft.EntityFrameworkCore.Query.Internal.QueryCompilationContextFactory.get_TrackQueryResults() 在 Microsoft.EntityFrameworkCore.Query.Internal.QueryCompilationContextFactory.Create(Boolean async) 在 Microsoft.EntityFrameworkCore.Storage.Database.CompileQuery[TResult](QueryModelqueryModel) 在 Blueshift.EntityFrameworkCore.MongoDB.Storage.MongoDbDatabase.<>c__DisplayClass11_01.<CompileAsyncQuery>b__0(QueryContext queryContext) 在 Microsoft.EntityFrameworkCore.Query.Internal.QueryCompiler.ExecuteAsync[TResult](表达式查询) 在 Microsoft.EntityFrameworkCore.Query.Internal.EntityQueryProvider.ExecuteAsync[TResult](表达式表达式) 在 Microsoft.EntityFrameworkCore.Query.Internal.EntityQueryable1.System.Collections.Generic.IAsyncEnumerable.GetEnumerator() 在 System.Linq.AsyncEnumerable.Aggregate_[TSource,TAccumulate,TResult](IAsyncEnumerable1 source, TAccumulate seed, FuncD:\a\1\s\Ix.NET\Source\System.Interactive.Async\Aggregate.cs:line 118 中的 3 个累加器、Func2 resultSelector、CancellationToken CancellationToken) 在 C:\Users\Adam\Repositories\TeamJob\TeamJob\src\Services\Profile 中的 Profile.Application.Profiles.Queries.GetAllProfiles.GetAllProfilesQueryHandler.Handle(GetAllProfilesQuery request, CancellationToken cancelationToken)\Profile.Application\Profiles\Queries\GetAllProfiles\GetAllProfilesQueryHandler.cs:line 24 at MediatR.Pipeline.RequestPostProcessorBehavior2.Handle(TRequest request, CancellationToken cancelationToken, RequestHandlerDelegate1 next) 在 MediatR.Pipeline.RequestPreProcessorBehavior2.Handle(TRequest request, CancellationToken cancellingToken, RequestHandlerDelegate1 next) at MediatR.Pipeline.RequestPreProcessorBehavior2.Handle(TRequest request, CancellationTokenCancellationToken, RequestHandlerDelegate1 next) at Profile.API.Controllers.ProfilesController.GetAll() 在 C:\Users\Adam\Repositories\TeamJob\TeamJob\src\Services\Profile\Profile.API\Controllers\ProfilesController.cs:line 19 在 lambda_method(Closure , Object ) 在 Microsoft.Extensions.Internal.ObjectMethodExecutorAwaitable.Awaiter.GetResult() 在 Microsoft.AspNetCore.Mvc.Internal.ActionMethodExecutor.AwaitableObjectResultExecutor.Execute(IActionResultTypeMapper mapper, ObjectMethodExecutor, 对象控制器, Object[] 参数) 在 System.Threading.Tasks.ValueTask1.get_Result() 在 Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.InvokeActionMethodAsync() 在 Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.InvokeNextActionFilterAsync() 在 Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.Rethrow(ActionExecutedContext context) 在 Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.Next(State&接下来,范围&范围、对象&状态,布尔&已完成) 在 Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.InvokeInnerFilterAsync() 在 Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.InvokeNextExceptionFilterAsync()"}

{ "error": [ "Cannot access a disposed object. A common cause of this error is disposing a context 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, or wrapping the context in a using statement. If you are using dependency injection, you should let the dependency injection container take care of disposing context instances. Object name: 'ProfileDbContext'." ], "stackTrace": " at Microsoft.EntityFrameworkCore.DbContext.CheckDisposed() at Microsoft.EntityFrameworkCore.DbContext.get_InternalServiceProvider() at Microsoft.EntityFrameworkCore.DbContext.get_ChangeTracker() at Microsoft.EntityFrameworkCore.Query.Internal.QueryCompilationContextFactory.get_TrackQueryResults() at Microsoft.EntityFrameworkCore.Query.Internal.QueryCompilationContextFactory.Create(Boolean async) at Microsoft.EntityFrameworkCore.Storage.Database.CompileQuery[TResult](QueryModel queryModel) at Blueshift.EntityFrameworkCore.MongoDB.Storage.MongoDbDatabase.<>c__DisplayClass11_01.<CompileAsyncQuery>b__0(QueryContext queryContext) at Microsoft.EntityFrameworkCore.Query.Internal.QueryCompiler.ExecuteAsync[TResult](Expression query) at Microsoft.EntityFrameworkCore.Query.Internal.EntityQueryProvider.ExecuteAsync[TResult](Expression expression) at Microsoft.EntityFrameworkCore.Query.Internal.EntityQueryable1.System.Collections.Generic.IAsyncEnumerable.GetEnumerator() at System.Linq.AsyncEnumerable.Aggregate_[TSource,TAccumulate,TResult](IAsyncEnumerable1 source, TAccumulate seed, Func3 accumulator, Func2 resultSelector, CancellationToken cancellationToken) in D:\a\1\s\Ix.NET\Source\System.Interactive.Async\Aggregate.cs:line 118 at Profile.Application.Profiles.Queries.GetAllProfiles.GetAllProfilesQueryHandler.Handle(GetAllProfilesQuery request, CancellationToken cancellationToken) in C:\Users\Adam\Repositories\TeamJob\TeamJob\src\Services\Profile\Profile.Application\Profiles\Queries\GetAllProfiles\GetAllProfilesQueryHandler.cs:line 24 at MediatR.Pipeline.RequestPostProcessorBehavior2.Handle(TRequest request, CancellationToken cancellationToken, RequestHandlerDelegate1 next) at MediatR.Pipeline.RequestPreProcessorBehavior2.Handle(TRequest request, CancellationToken cancellationToken, RequestHandlerDelegate1 next) at MediatR.Pipeline.RequestPreProcessorBehavior2.Handle(TRequest request, CancellationToken cancellationToken, RequestHandlerDelegate1 next) at Profile.API.Controllers.ProfilesController.GetAll() in C:\Users\Adam\Repositories\TeamJob\TeamJob\src\Services\Profile\Profile.API\Controllers\ProfilesController.cs:line 19 at lambda_method(Closure , Object ) at Microsoft.Extensions.Internal.ObjectMethodExecutorAwaitable.Awaiter.GetResult() at Microsoft.AspNetCore.Mvc.Internal.ActionMethodExecutor.AwaitableObjectResultExecutor.Execute(IActionResultTypeMapper mapper, ObjectMethodExecutor executor, Object controller, Object[] arguments) at System.Threading.Tasks.ValueTask1.get_Result() at Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.InvokeActionMethodAsync() at Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.InvokeNextActionFilterAsync() at Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.Rethrow(ActionExecutedContext context) at Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted) at Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.InvokeInnerFilterAsync() at Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.InvokeNextExceptionFilterAsync()" }

推荐答案

问题出在 Mediator.Send 方法.Mediator 类将请求处理程序存储在静态 ConcurrentDictionary

The problem is in Mediator.Send method. Mediator class stores request handlers in a static ConcurrentDictionary

private static readonly ConcurrentDictionary<Type, object> _requestHandlers = new ConcurrentDictionary<Type, object>();

Send 方法被调用时,它会对该字典使用 GetOrAdd 方法.

and when the Send method is invoked, it uses GetOrAdd method on that dictionary.

var handler = (RequestHandlerWrapper<TResponse>)_requestHandlers.GetOrAdd(requestType, t => Activator.CreateInstance(typeof(RequestHandlerWrapperImpl<,>).MakeGenericType(requestType, typeof(TResponse))));

这意味着,如果请求处理程序实例不存在于字典中,它会创建一个新实例(使用Activator)并将其添加到字典中,但如果请求处理程序实例已经存在存在于字典中,它使用现有的(这就是您的问题的原因).

This means, if the request handler instance doesn't already exist in the dictionary, it creates a new instance (using Activator) and adds it to the dictionary, but if the request handler instance already exists in the dictionary, it uses the existing one (and this is the cause of your problem).

那么,究竟是什么导致了您的错误?

_requestHandlers 字典是static,这意味着它通过多个请求,即最后不会被处理/垃圾收集的请求.您的 ProfileDbContext,在使用 AddDbContext 方法注册时,有一个 范围生命周期,这意味着它每个请求创建一次(并在结束时处理)请求).这意味着您可能会遇到这样的情况:_requestHandlers 字典包含一个 GetAllProfilesQueryHandler 实例,该实例具有对 ProfileDbContext 公开实例的引用.

The _requestHandlers dictionary is static, which means it lives through multiple requests, i.e. doesn't get disposed/garbage collected at the end of the request. Your ProfileDbContext, when registered using AddDbContext method, has a scoped lifetime, which means it is created once per request (and disposed at the end of the request). This means you can end up in a situation where _requestHandlers dictionary contains an instance of GetAllProfilesQueryHandler that has a reference on an exposed instance of ProfileDbContext.

这是发生了什么:

  1. 第一个请求到达.
  2. Mediator.Send(new GetProfileQuery { Id = id }) 被调用.
  3. Mediator.Send(new GetProfileQuery { Id = id })_requestHandlers 字典中找不到 GetAllProfilesQueryHandler,所以它实例化它并还解决了它的 ProfileDbContext 依赖项.
  4. 请求结束._context 字段 (ProfileDbContext) 在您的 GetAllProfilesQueryHandler 实例中被释放(因为它有一个 scoped 生命周期),但是_requestHandlers 字典(包含 GetAllProfilesQueryHandler 实例)不会被处理(因为它是静态的).
  5. 另一个请求到达.
  6. Mediator.Send(new GetProfileQuery { Id = id }) 再次被调用.
  7. 这次Mediator.Send(new GetProfileQuery { Id = id })_requestHandlers 字典中找到GetAllProfilesQueryHandler 实例并使用现有实例,其 _context 字段位于前一个请求的末尾.
  8. GetAllProfilesQueryHandler 尝试访问已处理的 _context 字段,并收到无法访问已处理的对象"错误.
  1. First request arrives.
  2. Mediator.Send(new GetProfileQuery { Id = id }) gets called.
  3. Mediator.Send(new GetProfileQuery { Id = id }) doesn't find GetAllProfilesQueryHandler in _requestHandlers dictionary, so it instantiates it and also resolves its ProfileDbContext dependency.
  4. The request ends. _context field (ProfileDbContext) in your GetAllProfilesQueryHandler indsance gets disposed (because it has a scoped lifetime), but the _requestHandlers dictionary (containing GetAllProfilesQueryHandler instance) doesn't get disposed (because it is static).
  5. Another request arrives.
  6. Mediator.Send(new GetProfileQuery { Id = id }) gets called again.
  7. This time Mediator.Send(new GetProfileQuery { Id = id }) finds GetAllProfilesQueryHandler instance in _requestHandlers dictionary and uses the existing instance, whose _context field is disposed at the end of the previous request.
  8. GetAllProfilesQueryHandler tries to access the disposed _context field, and gets the "Cannot access a disposed object" error.

可能的解决方案

不要让 Mediator.Send 解析 GetAllProfilesQueryHandler 的依赖项.

Don't let Mediator.Send resolve GetAllProfilesQueryHandlers dependencies.

也许将 IServiceProvider serviceProvider 传递给您的 GetAllProfilesQueryHandler 并让它根据需要解析其依赖项:

Maybe pass IServiceProvider serviceProvider to your GetAllProfilesQueryHandler and let it resolve its dependencies as needed:

public class GetAllProfilesQueryHandler : IRequestHandler<GetAllProfilesQuery, ProfilesListViewModel>
{
    private readonly IServiceProvider _serviceProvider;

    public GetAllProfilesQueryHandler(IServiceProvider serviceProvider)
    {
        _serviceProvider = serviceProvider;
    }

    public async Task<ProfilesListViewModel> Handle(GetAllProfilesQuery request, CancellationToken cancellationToken)
    {
        return new ProfilesListViewModel
        {
            ProfileDbContext context = (ProfileDbContext)this._serviceProvider.GetService(typeof(ProfileDbContext));
            IMapper mapper = (IMapper)this._serviceProvider.GetService(typeof(IMapper));

            Profiles = await context.Profiles.ProjectTo<ProfileLookupModel>(mapper.ConfigurationProvider).ToListAsync(cancellationToken)
        };
    }
}

正如@Lucian Bargaoanu 在评论中指出的,您可以通过 DI 解析处理程序,如 https://github.com/jbogard/MediatR.Extensions.Microsoft.DependencyInjection

As @Lucian Bargaoanu pointed out in comments, you can resolve handlers through DI, as in https://github.com/jbogard/MediatR.Extensions.Microsoft.DependencyInjection

这篇关于无法访问用于异步方法的 .NET Core 中 DbContext 的已释放对象的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
相关文章
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆