Не удается получить доступ к удаленному объекту для DbContext в .NET Core для асинхронного метода - PullRequest
2 голосов
/ 08 апреля 2019

У меня странная проблема в одном из моих веб-интерфейсов microservice.Мои асинхронные методы GET выдают Невозможно получить доступ к удаленному объекту исключение для моего DbContext, за исключением самого первого раза, когда они вызываются.Я пытался найти ответ в Интернете, но ничего не получалось.Я убедился, что мои методы не являются асинхронными, и жду необходимых вызовов.Поскольку мои методы 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; }
}

Сообщение об исключении

{"error": ["Невозможно получить доступ к удаленному объекту.Распространенной причиной этой ошибки является удаление контекста, который был разрешен путем внедрения зависимости, а затем попытка использовать тот же экземпляр контекста в другом месте вашего приложения. Это может произойти, если вы вызываете Dispose () для контекста или переносите контекст воператор using. Если вы используете внедрение зависимостей, вы должны позволить контейнеру внедрения зависимостей позаботиться об удалении экземпляров контекста. \ r \ nОбъект объекта: 'ProfileDbContext'. "], "stackTrace": "в Microsoft.EntityFrameworkCore.DbContext.CheckDisposed () \ r \ n в Microsoft.EntityFrameworkCore.DbContext.get_InternalServiceProvider () \ r \ n в Microsoft.EntityFrameworkCore.DbContext.get \ r \ r \ nMicrosoft.EntityFrameworkCore.Query.Internal.QueryCompilationContextFactory.get_TrackQueryResults () \ r \ n в Microsoft.EntityFrameworkCore.Query.Internal.QueryCompilationContextFactory.Create.(QueryModel queryModel).[TSource, TAccumulate, TResult] (аккумулятор IAsyncEnumerable 1 source, TAccumulate seed, Func 3, Func 2 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.RequestPostProcessorBehavior 2.Handle (запрос TRequest, CancellationToken cancellationToken, RequestHandlerDelegate 1 next)\r\n at MediatR.Pipeline.RequestPreProcessorBehavior 2.Handle (запрос TRequest, CancellationToken cancellationToken 2. RequestHand 10)andle (запрос TRequest, запрос CancellationToken, CancellationToken, RequestHandlerDelegate 1 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.ValueTask 1.get_Result () \ r \ n в Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.InvokeActionMethodAsync () \ r \ n.CollerInCore.InvokeNextActionFilterAsync () \ r \ n в Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.Rethrow (контекст ActionExecutedContext) \ r \ n в Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.Next (область & состояние, объект, область, потомisCompleted) \ r \ n в Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.InvokeInnerFilterAsync () \ r \ n в Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.InvokeNextExceptionFilterAsync () "} * 10 * * *

1 Ответ

0 голосов
/ 08 апреля 2019

Проблема в 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, имеет срок действия в области , что означает, что создается один раз за запрос (и удаляется в конце запроса). Это означает, что вы можете оказаться в ситуации, когда словарь _requestHandlers содержит экземпляр GetAllProfilesQueryHandler, который имеет ссылку на открытый экземпляр ProfileDbContext.

Вот что происходит:

  1. Первый запрос поступил.
  2. Mediator.Send(new GetProfileQuery { Id = id }) вызывается.
  3. Mediator.Send(new GetProfileQuery { Id = id }) не находит GetAllProfilesQueryHandler в словаре _requestHandlers, поэтому он создает его и также разрешает зависимость ProfileDbContext.
  4. Запрос заканчивается. Поле _context (ProfileDbContext) в вашем GetAllProfilesQueryHandler indsance удаляется (поскольку оно имеет scoped время жизни), но словарь _requestHandlers (содержащий экземпляр GetAllProfilesQueryHandler) не удаляется (потому что оно статический).
  5. Приходит еще один запрос.
  6. Mediator.Send(new GetProfileQuery { Id = id }) снова вызывается.
  7. На этот раз Mediator.Send(new GetProfileQuery { Id = id }) находит GetAllProfilesQueryHandler экземпляр в _requestHandlers словаре и использует существующий экземпляр, поле которого _context расположено в конце предыдущего запроса .
  8. GetAllProfilesQueryHandler пытается получить доступ к удаленному полю _context и получает сообщение об ошибке «Не удается получить доступ к удаленному объекту».

Возможное решение

Не позволяйте Mediator.Send разрешать GetAllProfilesQueryHandler s зависимости.

Может быть, передать 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)
        };
    }
}

Edit:

Как отметил @Lucian Bargaoanu в комментариях, вы можете разрешать обработчики через DI, как в https://github.com/jbogard/MediatR.Extensions.Microsoft.DependencyInjection

...