Pic*_*ic8 4 c# async-await entity-framework-core .net-core asp.net-core-webapi
我的一个微服务 Web api 遇到了一个奇怪的问题。我的异步 GET 方法会抛出无法访问 DbContext 的已处置对象异常,除非第一次调用它们。我尝试在网上寻找答案,但没有任何效果。我确保我的方法不是 async void,并且我等待必要的调用。由于我的 POST 和 DELETE 方法工作正常,我相当确定真正的罪魁祸首是 IMapper 实例。我认为它可能总是指向 DbContext 的第一个实例,这就是为什么第一次可以工作但之后就不行的原因。任何帮助或指示将不胜感激
这是代码的一些快照。
启动.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")));
...
Run Code Online (Sandbox Code Playgroud)
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 }));
}
Run Code Online (Sandbox Code Playgroud)
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)
};
}
}
Run Code Online (Sandbox Code Playgroud)
ProfileDbContext.cs
[MongoDatabase("profileDb")]
public class ProfileDbContext : DbContext
{
public ProfileDbContext(DbContextOptions<ProfileDbContext> options)
: base(options)
{
}
public DbSet<Domain.Entities.Profile> Profiles { get; set; }
}
Run Code Online (Sandbox Code Playgroud)
异常信息
{ "error": [ "无法访问已处置的对象。此错误的常见原因是处置从依赖项注入解析的上下文,然后尝试在应用程序的其他位置使用相同的上下文实例。如果您在上下文上调用 Dispose(),或将上下文包装在 using 语句中。如果您使用依赖项注入,则应让依赖项注入容器负责处置上下文实例。\r\n对象名称:“ProfileDbContext”。], "stackTrace": " 在 Microsoft.EntityFrameworkCore.DbContext.CheckDispose()\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(布尔异步)\r\n 在 Microsoft.EntityFrameworkCore.Storage.Database.CompileQuery[TResult] (QueryModel queryModel)\r\n 在 Blueshift.EntityFrameworkCore.MongoDB.Storage.MongoDbDatabase.<>c__DisplayClass11_0
1.<CompileAsyncQuery>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请求,CancellationToken取消令牌,RequestHandlerDelegate1 next)\r\n at MediatR.Pipeline.RequestPreProcessorBehavior2.Handle(TRequest请求,CancellationToken取消令牌,RequestHandlerDelegate1 next)\r\n at MediatR.Pipeline.RequestPreProcessorBehavior2.Handle(TRequest请求,CancellationToken取消令牌,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(状态& next、范围&范围、对象&状态、布尔& isCompleted)\r\n 位于 Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.InvokeInnerFilterAsync( )\r\n 在 Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.InvokeNextExceptionFilterAsync()" }
问题出在Mediator.Send方法中。Mediator类将请求处理程序存储在静态ConcurrentDictionary中
private static readonly ConcurrentDictionary<Type, object> _requestHandlers = new ConcurrentDictionary<Type, object>();
Run Code Online (Sandbox Code Playgroud)
当Send调用该方法时,它会使用GetOrAdd该字典上的方法。
var handler = (RequestHandlerWrapper<TResponse>)_requestHandlers.GetOrAdd(requestType, t => Activator.CreateInstance(typeof(RequestHandlerWrapperImpl<,>).MakeGenericType(requestType, typeof(TResponse))));
Run Code Online (Sandbox Code Playgroud)
这意味着,如果请求处理程序实例尚不存在于字典中,它将创建一个新实例(使用Activator)并将其添加到字典中,但如果请求处理程序实例已存在于字典中,它将使用现有实例(这就是你的问题的原因)。
那么,究竟是什么导致了您的错误?
字典_requestHandlers是static,这意味着它可以通过多个请求生存,即在请求结束时不会被丢弃/收集垃圾。ProfileDbContext当使用方法注册时,您的AddDbContext具有范围生命周期,这意味着它每个请求创建一次(并在请求结束时处置)。这意味着您最终可能会遇到这样的情况:_requestHandlers字典包含一个 的实例,GetAllProfilesQueryHandler该实例具有对 的公开实例的引用ProfileDbContext。
发生的情况如下:
Mediator.Send(new GetProfileQuery { Id = id })被叫。Mediator.Send(new GetProfileQuery { Id = id })GetAllProfilesQueryHandler在字典中找不到_requestHandlers,因此它实例化它并解决它的ProfileDbContext依赖关系。_context字段 ( )被释放(因为它有生命周期),但字典(包含实例)不会被释放(因为它是静态的)。ProfileDbContextGetAllProfilesQueryHandlerscoped_requestHandlersGetAllProfilesQueryHandlerMediator.Send(new GetProfileQuery { Id = id })再次被呼叫。Mediator.Send(new GetProfileQuery { Id = id })查找GetAllProfilesQueryHandler实例并使用现有实例,该实例的字段在上一个请求的末尾被释放。_requestHandlers_contextGetAllProfilesQueryHandler尝试访问已处置_context字段,并收到“无法访问已处置对象”错误。可能的解决方案
不要让Mediator.SendresolveGetAllProfilesQueryHandler依赖。
也许传递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)
};
}
}
Run Code Online (Sandbox Code Playgroud)
编辑:
正如 @Lucian Bargaoanu 在评论中指出的那样,您可以通过 DI 解析处理程序,如https://github.com/jbogard/MediatR.Extensions.Microsoft.DependencyInjection中所示
| 归档时间: |
|
| 查看次数: |
6449 次 |
| 最近记录: |