System.ObjectDisposedException: Cannot access a disposed object, ASP.NET Core 3.1

≡放荡痞女 提交于 2021-01-29 18:09:22

问题


I am writing an API using SignalR in ASP.NET Core 3.1. I am totally new to .NET Core, and am pretty new to SignalR too. I am having an issue executing MongoDB (Atlas) queries which are running in transactions. It appears that the transaction sessions are expiring before the queries are executing. I'm quite sure it's a async/await issue, but don't seem to be able to fix it.

My Hub method looks like this:

public async Task<bool> UpdateProfile(object profileDto)
{
  try
  {
    ProfileDTO profile = ((JsonElement) profileDto).ToObject<ProfileDTO>();
    _profile.UpdateProfile(profile);
    return true;
  }
  catch
  {
    return false;
  }
}

And the _profile.UpdateProfile() method looks like this:

public void UpdateProfile(ProfileDTO profileDto)
{
  _databaseService.ExecuteInTransaction(async session =>
  {
    var profile = _mapper.Map<ProfileModel>(profileDto);
    var existingUser = (await _userCollectionService
        .FindAsync(session, user => user.Profille.Sub == profileDto.sub)
      ).FirstOrDefault();

    if (existingUser == null)
    {
      var newUser = new UserModel();
      newUser.Profille = profile;
      await _userCollectionService.CreateAsync(session, newUser);
    }
    else
    {
      existingUser.Profille = profile;
      // the error occurs on the following line
      await _userCollectionService.UpdateAsync(session, existingUser.Id, existingUser);
    }
  });
}

My ExecuteInTransaction() method is an attempt to generalise the transaction/session process, and looks like this:

public async void ExecuteInTransaction(DatabaseAction databaseAction)
{
  using (var session = await Client.StartSessionAsync())
  {
    try
    {
      session.StartTransaction();
      databaseAction(session);
      await session.CommitTransactionAsync();
    }
    catch (Exception e)
    {
      await session.AbortTransactionAsync();
      throw e;
    }
  }
}

I have indicated the line in the UpdateProfile() where the error occurs. The full error looks like this:

System.ObjectDisposedException: Cannot access a disposed object. Object name: 'MongoDB.Driver.Core.Bindings.CoreSessionHandle'. at MongoDB.Driver.Core.Bindings.WrappingCoreSession.ThrowIfDisposed() at MongoDB.Driver.Core.Bindings.WrappingCoreSession.get_IsInTransaction() at MongoDB.Driver.ClientSessionHandle.get_IsInTransaction() at MongoDB.Driver.MongoCollectionImpl1.CreateBulkWriteOperation(IClientSessionHandle session, IEnumerable1 requests, BulkWriteOptions options) at MongoDB.Driver.MongoCollectionImpl1.BulkWriteAsync(IClientSessionHandle session, IEnumerable1 requests, BulkWriteOptions options, CancellationToken cancellationToken) at MongoDB.Driver.MongoCollectionBase1.ReplaceOneAsync(FilterDefinition1 filter, TDocument replacement, ReplaceOptions options, Func3 bulkWriteAsync) at IndistinctChatter.API.Services.Business.Profile.<>c__DisplayClass5_0.<<UpdateProfile>b__0>d.MoveNext() in /Volumes/Data/Users/marknorgate/Documents/Development/source/indistinct-chatter/api-dotnet/Services/Business/Profile.cs:line 48 --- End of stack trace from previous location where exception was thrown --- at System.Threading.Tasks.Task.<>c.<ThrowAsync>b__139_1(Object state) at System.Threading.QueueUserWorkItemCallback.<>c.<.cctor>b__6_0(QueueUserWorkItemCallback quwi) at System.Threading.ExecutionContext.RunForThreadPoolUnsafe[TState](ExecutionContext executionContext, Action1 callback, TState& state) at System.Threading.QueueUserWorkItemCallback.Execute() at System.Threading.ThreadPoolWorkQueue.Dispatch() at System.Threading._ThreadPoolWaitCallback.PerformWaitCallback()

So it seems the session object is expiring before the await _userCollectionService.UpdateAsync(session, existingUser.Id, existingUser); line is executing. The line await session.CommitTransactionAsync(); is executing first.

Where am I going wrong? I feel a Task coming on...


回答1:


Your DatabaseAction is not an asynchronous delegate. It seems to be Action<T> but what you really need is Func<T, Task>. So when you do async session => that code gets turned into an async void signature. Due to it being void, you cannot await it. This leads to your async code being scheduled to a threadpool thread and the call immediately returns. This has the knock on effect that await session.CommitTransactionAsync() commits while potentially, your delegate hasn't even started running yet. Your code inside ExecuteInTransaction is now considered "done" and exits, disposing your session along the way due to using block.

To fix this, you need to change your DatabaseAction's signature and then await databaseAction(session);




回答2:


Ok, I have found a work around, although I am not entirely happy with the solution.

I discovered the AutoResetEvent(), so modified my code like this:

public async void ExecuteInTransaction(DatabaseAction databaseAction)
{
  AutoResetEvent autoResetEvent = new AutoResetEvent(false);

  using var session = await Client.StartSessionAsync();

  try
  {
    session.StartTransaction();
    databaseAction(session, autoResetEvent);
    autoResetEvent.WaitOne();
    await session.CommitTransactionAsync();
  }
  catch (Exception e)
  {
    await session.AbortTransactionAsync();
    throw e;
  }
}

and

public void UpdateProfile(ProfileDTO profileDto)
{
  _databaseService.ExecuteInTransaction(async (session, autoResetEvent) =>
  {
    var profile = _mapper.Map<ProfileModel>(profileDto);
    var existingUser = (await _userCollectionService
        .FindAsync(session, user => user.Profille.Sub == profileDto.sub)
      ).FirstOrDefault();

    if (existingUser == null)
    {
      var newUser = new UserModel();
      newUser.Profille = profile;
      await _userCollectionService.CreateAsync(session, newUser);
    }
    else
    {
      existingUser.Profille = profile;
      await _userCollectionService.UpdateAsync(session, existingUser.Id, existingUser);
    }

    autoResetEvent.Set();
  });
}

Seems to work, but it is an extra step at the end of each database operation that I need to remember. If anyone can improve on this I would be pleased to hear about it!



来源:https://stackoverflow.com/questions/64451521/system-objectdisposedexception-cannot-access-a-disposed-object-asp-net-core-3

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!