Matias Cicero
Matias Cicero

Reputation: 26281

Do multiple DbContext generate a deadlock?

Summarizing my code, I have a IRepository<E> that uses a DbContextStrategy<E>.

DbContextStrategy<E> extends from DbContext and uses a DbSet<E> for LINQ operations on the database.

E is just an entity type passed by generics.

In my Web API controller I am using this repository interface to fetch my resources.

However, some entities depend on other entities. And in PUT or POST requests for example, I should validate input foreign keys to check if they are valid.

As a result, I would need to instantiate a new IRepository<X> where X is the foreign entity type.

In order for me to make my development as easy as possible, I generated a layer of base classes that will handle configuration, cache, dependency injection and HTTP method binding for me.

In my least child controller I have the following method:

/// Base class provides me with the Season entity fetched from the database.
/// An IRepository<Season> is open in the background, and therefore, a DbContext too.
protected override Season UpdateEntity(Season entity, SeasonPostDTO dto)
{
     Task<bool> validatingShow = ValidateShow(dto.Show);

     entity.Number = dto.Number;
     entity.ReleaseDate = dto.ReleaseDate;
     entity.Image = dto.Image;
     entity.TVShowId = dto.Show;
     entity.BlameUserId = Convert.ToInt32(GetCurrentUserId());

     validatingShow.Wait();
     if (!validatingShow.Result)
          throw new EntityNotFoundException("Show not found");

     return entity;
} 

This is the method that will handle the entity's update. The base controller will call it, and then call the repository.Edit(entity) which will update the entity in the DbContext. After the operation, the IRepository<Season> is disposed.

ValidateShow is a private method that just checks if a showId exists:

private async Task<bool> ValidateShow(int id) {

     //This will instantiate a new IRepository<TVShow>, and therefore a new DbContext
     return await UseAsyncDependency<TVShow, bool>(async (showRepo) => {

         return (await showRepo.ReadAsync(id)) != null;

     });
}

However, the ValidateShow method just stays in an infinite loop. I have debugged the method, and the call gets correctly delegated to the DbSet<TVShow> and the loop happens at: context.Entities.FindAsync(keys).

The method works ok, because I used the same ReadAsync method to fetch the Season entity.

But it appears that it generates some kind of deadlock when there are two different DbContext open. (DbSet<Season> and DbSet<TVShow>)

I should note that both DbContext connects to the same database.

Async / Await execution flow from IRepository to DbSet

IRepository<E> calls the SelectAsync(keys) method on an IDao<E>, which calls the SelectAsync(keys) method on DbContextStrategy<E>.

Here is the code trace:

DefaultRepository : IRepository:

public async Task<E> ReadAsync(params object[] keys) {
    if(keys == null || keys.Length < 1) return null;
    return await dao.SelectAsync(keys);
}

DefaultDao : IDao

public async Task<E> SelectAsync(params object[] keys) {
    return await ForEachStrategyAsync(async (strategy) => {
        return await strategy.SelectAsync(keys);
    }, (entity) => {
        return entity != null;
    });
}

private async Task<R> ForEachStrategyAsync<R>(Func<IPersistenceStrategy<E>, Task<R>> function,
                                              Func<R, bool> assertion) {

    R lastResult = default(R);
    foreach(IPersistenceStrategy<E> strategy in strategies) {
         lastResult = await function(strategy);
         if(assertion(lastResult)) break;
    }
    return lastResult;
}

DbContextStrategy : IPersistenceStrategy

public async Task<E> SelectAsync(params object[] keys) {
    return await context.Entities.FindAsync(keys);
}

Upvotes: 3

Views: 1594

Answers (1)

Michael Domashchenko
Michael Domashchenko

Reputation: 1480

Each instance of DbContext has its own transaction which might cause deadlock but you'd need a write operation for that to happen and DbContext only saves the changes to the database when you call SaveChanges() which you don't call before calling validatingShow.Wait()

It's much more likely that this Wait() call is a problem. If you're using async/await you should really use async/await for the whole call stack (UpdateEntity() and up including your Web API controller's methods)

The reason why this is happening is described here http://blog.stephencleary.com/2012/07/dont-block-on-async-code.html

Upvotes: 3

Related Questions