【问题标题】:Cannot access a disposed object for DbContext in .NET Core for Async method无法在 .NET Core for Async 方法中访问 DbContext 的已处置对象
【发布时间】:2019-08-29 04:39:23
【问题描述】:

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

这里是代码的一些快照。

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

    // 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

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

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

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

异常信息

{ “错误”: [ “无法访问已处置的对象。此错误的常见原因是处置从依赖注入解决的上下文,然后尝试在应用程序的其他地方使用相同的上下文实例。如果您在" ], "stackTrace": " 在 Microsoft.EntityFrameworkCore.DbContext.CheckDisposed()\r\n 在 Microsoft.EntityFrameworkCore.DbContext.get_InternalServiceProvider()\r\n 在 Microsoft.EntityFrameworkCore.DbContext.get_ChangeTracker()\r\n 在 Microsoft。 EntityFrameworkCore.Query.Internal.QueryCompilationContextFactory.get_TrackQueryResults()\r\n 在 Microsoft.EntityFrameworkCore.Query.Internal.QueryCompilationContextFactory.Create(Boolean async)\r\n 在 Microsoft.EntityFrameworkCore.Storage.Database.CompileQuery[TResult](QueryModel queryModel)\r\n 在 Blueshift.EntityFrameworkCore.MongoDB.Storage.MongoDbDatabase.c__DisplayClass11_01.&lt;CompileAsyncQuery&gt;b__0(QueryContext queryContext)\r\n at Microsoft.EntityFrameworkCore.Query.Internal.QueryCompiler.ExecuteAsync[TResult](Expression query)\r\n at Microsoft.EntityFrameworkCore.Query.Internal.EntityQueryProvider.ExecuteAsync[TResult](Expression expression)\r\n at Microsoft.EntityFrameworkCore.Query.Internal.EntityQueryable1.System.Collections.Generic.IAsyncEnumerable.GetEnumerator()\r\n 在 System.Linq.AsyncEnumerable.Aggregate_[TSource ,TAccumulate,TResult](IAsyncEnumerable1 source, TAccumulate seed, Func3 累加器, Func2 resultSelector, CancellationToken cancellationToken) in D:\\a\\1\\s\\Ix.NET\\Source\\System.Interactive.Async\\Aggregate.cs:line 118\r\n 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\r\n at MediatR.Pipeline.RequestPostProcessorBehavior2.Handle(TRequest request, CancellationToken cancelToken, RequestHandlerDelegate1 next)\r\n at MediatR.Pipeline.RequestPreProcessorBehavior2.Handle(TRequest request, CancellationToken cancelToken , RequestHandlerDelegate1 next)\r\n at MediatR.Pipeline.RequestPreProcessorBehavior2.Handle(TRequest request, CancellationToken cancelToken, RequestHandlerDelegate1 next)\r\n at Profile.API.Controllers.ProfilesController.GetAll() in C:\\Users\\Adam\\Repositories\\TeamJob\\TeamJob\\src\\Services\\Profile\\Profile.API\\Controllers\\ProfilesController.cs:line 19\r\n at lambda_method(Closure , Object )\r\n at Microsoft.Extensions.Internal.ObjectMethodExecutorAwaitable.Awaiter.GetResult()\r\n at Microsoft.AspNetCore.Mvc.Internal.ActionMethodExecutor.AwaitableObjectResultExecutor.Execute(IActionResultTypeMapper mapper, ObjectMethodExecutor executor, Object controller, Object[] arguments)\r\n at System.Threading.Tasks.ValueTask1.get_Result()\r\n 在 Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.InvokeActionMethodAsync()\r\n 在 Microsoft.AspNetCore .Mvc.Internal.ControllerActionInvoker.InvokeNextActionFilterAsync()\r\n 在 Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.Rethrow(ActionExecutedContext 上下文)\r\n 在 Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.Next(State& next,范围和范围、对象和状态、布尔值& isCompleted)\r\n 在 Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.InvokeInnerFilterAsync()\r\n 在 Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.InvokeNextExceptionFilterAsync()" }

【问题讨论】:

  • 看起来你的处理程序注册为单例而不是每个请求/范围。
  • Mediator.Send 是您的实现还是您使用了一些第三方库?您能否提供有关该方法的更多信息,因为我认为您的问题的根源在于它以及您的GetAllProfilesQueryHandler 正在解决的方式。
  • @MarkoPapic 我正在为此使用 3rdParty 库 MediatR。当我下班回家时,我会添加更多关于此的信息。
  • 哇,我昨天刚刚遇到了同样的问题。扯掉我的头发。我第一次看到它以及我一直使用的 EF 核心操作

标签: c# .net-core async-await entity-framework-core asp.net-core-webapi


【解决方案1】:

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

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

当调用Send 方法时,它会在该字典上使用GetOrAdd 方法。

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

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

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

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

会发生什么:

  1. 第一个请求到达。
  2. Mediator.Send(new GetProfileQuery { Id = id }) 被调用。
  3. Mediator.Send(new GetProfileQuery { Id = id })_requestHandlers 字典中没有找到 GetAllProfilesQueryHandler,因此它实例化它并解析其 ProfileDbContext 依赖项。
  4. 请求结束。 _context 字段 (ProfileDbContext) 在您的 GetAllProfilesQueryHandler indsance 被处理(因为它有一个 scoped 生命周期),但 _requestHandlers 字典(包含 GetAllProfilesQueryHandler 实例)没有被处理(因为它是静态)。
  5. 另一个请求到达。
  6. Mediator.Send(new GetProfileQuery { Id = id }) 再次被调用。
  7. 这一次Mediator.Send(new GetProfileQuery { Id = id })_requestHandlers字典中找到GetAllProfilesQueryHandler实例并使用现有实例,其_context字段被放置在上一个请求的末尾
  8. GetAllProfilesQueryHandler 尝试访问已处置的 _context 字段,并收到“无法访问已处置的对象”错误。

可能的解决方案

不要让Mediator.Send 解决GetAllProfilesQueryHandlers 的依赖关系。

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

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 在 cmets 中指出的那样,您可以通过 DI 解析处理程序,如 https://github.com/jbogard/MediatR.Extensions.Microsoft.DependencyInjection

【讨论】:

  • 你应该像github.com/jbogard/…一样通过DI解析处理程序。
  • 为我工作。我想每隔一段时间调用一个函数,并且该函数需要在每个时间间隔访问 dbcontext。感谢@Marko 的解决方案。
猜你喜欢
  • 2018-07-16
  • 1970-01-01
  • 1970-01-01
  • 2018-11-01
  • 2020-10-21
  • 2020-07-09
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多