3

2 methods from my controller for some reason trigger this error: An exception occurred in the database while saving changes for context type 'API.Data.DataContext'. System.InvalidOperationException: A second operation was started on this context before a previous operation completed. This is usually caused by different threads concurrently using the same instance of DbContext

I have tried everything in this thread: Entity Framework Core: Entity Framework Core: A second operation started on this context before a previous operation completed But nothing seems to work for me.

ITs different for me because I cant use dbContext as tranziant

The issue happens in PowerUp and DeleteHero methods

Controller

public class HeroesController : BaseApiController
{

    private readonly IUserRepository _userRepository;
    public readonly IHeroRepository _heroRepository;
    public HeroesController(IUserRepository userRepository, IHeroRepository heroRepository)
    {
        _userRepository = userRepository;
        _heroRepository = heroRepository;
    }

    [HttpPost("{create}")]
    public async Task<ActionResult> CreateHero(RegisterHeroDto registerHeroDto)
    {
        var user = await this.GetActiveUser();

        var existingUserHero = await _heroRepository.IsUserHasHero(user, registerHeroDto.Name);

        if (existingUserHero == true) { return BadRequest("You already have a hero with that name"); }

        var hero = _heroRepository.HeroBuilder(registerHeroDto, user);

        user.Heroes.Add(hero);

        if (await _userRepository.SaveAllAsync()) { return Ok(); }

        return BadRequest("Failed to create hero");
    }

    public async Task<ActionResult<IEnumerable<HeroDto>>> GetHeroes()
    {
        var user = await this.GetActiveUser();

        var heroesDto = await _heroRepository.HeroesDtoConverter(user);

        return Ok(heroesDto);
    }

    [HttpPost("powerUp/{id}")]
    public async Task<ActionResult> PowerUp(int id)
    {
        var user = await this.GetActiveUser();

        var hero = await _heroRepository.GetHeroById(id);

        var canHeroPowerUp = _heroRepository.PowerUpAuthorizer(hero);

        if (canHeroPowerUp == false)
        {
            return BadRequest(hero.Name + " has 0 power-ups left for today");
        }

        _heroRepository.PowerUp(hero);

        var heroesDto = await _heroRepository.HeroesDtoConverter(user);

        if (await _userRepository.SaveAllAsync()) { return Ok(); }

        return BadRequest("Failed to power up " + hero.Name);
    }

    [HttpDelete("{id}")]
    public async Task<ActionResult> DeleteHero(int id)
    {
        var user = await this.GetActiveUser();

        var hero = await _heroRepository.GetHeroById(id);

        _heroRepository.DeleteHero(user, hero);

        var heroesDto = await _heroRepository.HeroesDtoConverter(user);

        if (await _userRepository.SaveAllAsync()) { return Ok(); }

        return BadRequest("Failed to remove hero");
    }

    private Task<AppUser> GetActiveUser()
    {
        var userId = User.FindFirst(ClaimTypes.NameIdentifier)?.Value;
        var user = await this._userRepository.GetUserByIdAsync(userId);

        return user;
    }
}

Hero Repository:

    public class HeroRepository : ControllerBase, IHeroRepository
{
    private readonly DataContext _context;
    public readonly IMapper _mapper;
    public HeroRepository(DataContext context, IMapper mapper)
    {
        _context = context;
        _mapper = mapper;
    }

    public async Task<List<Hero>> GetAllHeroes(AppUser user)
    {
        // refreshPowerUps(user.Heroes.ToList());

        // var heroes = user.Heroes
        //             .OrderBy(a => a.CurrentPower)
        //             .ToList();

        var heroes = await _context.Heroes
                            .Where(a => a.AppUserId == user.Id)
                            .OrderBy(a => a.CurrentPower)
                            .ToListAsync();

        // var updatedHeroes = refreshPowerUps(heroes);

        return heroes;
    }

    // private async void refreshPowerUps(List<Hero> heroes)
    // {
    //     foreach (Hero hero in heroes)
    //     {
    //         if (hero.LastPowerUp.Date.Day != DateTime.Now.Day)
    //         {
    //             hero.PowerUpsToday = 0;
    //         }
    //     }
    //     await SaveAllAsync();
    // }

    public async Task<Hero> GetHeroById(int id)
    {
        return await _context.Heroes
                .Where(x => x.Id == id)
                .FirstOrDefaultAsync();
    }

    public Hero HeroBuilder(RegisterHeroDto registerHeroDto, AppUser user)
    {
        var startingPower = registerHeroDto.Ability == "attacker" ? 100 : 50;

        var hero = new Hero
        {
            Name = registerHeroDto.Name,
            Ability = registerHeroDto.Ability,
            SuitColors = registerHeroDto.SuitColors,
            StartingPower = startingPower,
            CurrentPower = startingPower,
            AppUserId = user.Id
        };

        return hero;
    }

    public async Task<bool> IsUserHasHero(AppUser user, string heroName)
    {
        if (user.Heroes == null)
        {
            user.Heroes = new List<Hero>();
        }

        var existingHero = await _context.Heroes
                            .Where(x => x.AppUserId == user.Id)
                            .FirstOrDefaultAsync(x => x.Name.ToLower() == heroName.ToLower());

        if (existingHero != null)
        {
            return true;
        }

        return false;
    }

    public bool PowerUpAuthorizer(Hero hero)
    {
        if (hero.LastPowerUp.Day == DateTime.Now.Day && hero.PowerUpsToday >= 5)
        {
            return false;
        }

        return true;
    }

    public double PowerUp(Hero hero)
    {
        float powerIncrement = ((new Random().Next(1, 10) * .1f) / 10) + 1;
        float newPower = (float)hero.CurrentPower * powerIncrement;

        hero.CurrentPower = newPower;
        hero.PowerUpsToday++;
        hero.LastPowerUp = DateTime.Now;

        return newPower;
    }

    public async Task<bool> SaveAllAsync()
    {
        return await _context.SaveChangesAsync() > 0;
    }

    public void DeleteHero(AppUser user, Hero hero)
    {
        user.Heroes.Remove(hero);
    }

    public async Task<List<HeroDto>> HeroesDtoConverter(AppUser user)
    {
        var heroes = await GetAllHeroes(user);

        var heroesDto = _mapper.Map<IList<Hero>, IList<HeroDto>>(heroes).ToList();

        return heroesDto;
    }
}

User repository

public class UserRepository : BaseApiController, IUserRepository
{
    private readonly DataContext _context;
    private readonly IMapper _mapper;
    private readonly UserManager<AppUser> _userManager;
    public UserRepository(DataContext context, IMapper mapper,
                          UserManager<AppUser> userManager)
    {
        _mapper = mapper;
        _context = context;
        _userManager = userManager;
    }

    public async Task<AppUser> GetUserByEmailAsync(string email)
    {
        return await _userManager.Users
           .SingleOrDefaultAsync(x => x.Email == email.ToLower());
    }

    public async Task<AppUser> GetUserByIdAsync(string id)
    {
        return await _context.Users
                .Where(x => x.Id == id)
                .Include(a => a.Heroes)
                .SingleOrDefaultAsync();
    }

    public async Task<AppUser> GetUserByUserNameAsync(string userName)
    {
        return await _userManager.Users
            .SingleOrDefaultAsync(x => x.UserName == userName.ToLower());
    }

    public async Task<bool> SaveAllAsync()
    {
        return await _context.SaveChangesAsync() > 0;
    }

    public void Update(AppUser user)
    {
        _context.Entry(user).State = EntityState.Modified;
    }


    public async Task<bool> UserExists(string email)
    {
        return await _userManager.Users.AnyAsync(x => x.Email == email.ToLower());
    }

}

dbContext Declartion

            services.AddDbContext<DataContext>(options =>
        {
            options.UseSqlServer(config.GetConnectionString("DefaultConnection"));
        });

It stopped working after I manipulated the Hero repository and I'm not able to figure out what causing it... I have tried everything in this thread: Entity Framework Core: A second operation started on this context before a previous operation completed But nothing seems to work for me. I don't really get why this type of issue happens. Can someone please explain to me what causes that issue? And how can I improve myself to avoid this type of error?

Thanks!!!!

John
  • 81
  • 1
  • 2
  • 10
  • why is `GetActiveUser` not awaiting the call to `_userRepository.GetUserByIdAsync(userId);`??? You have it returning a `Task` but did not mark it as `async` – Ryan Wilson Nov 22 '21 at 21:47
  • I fixed it but it still throughing the same error as before – John Nov 22 '21 at 21:51
  • Does this answer your question? [Entity Framework Core: A second operation started on this context before a previous operation completed](https://stackoverflow.com/questions/48767910/entity-framework-core-a-second-operation-started-on-this-context-before-a-previ) – Ryan Wilson Nov 22 '21 at 21:55
  • I wrote that i read this thread before posting this question and nothing worked :( @RyanWilson – John Nov 22 '21 at 21:56

2 Answers2

2

Both PowerUp and DeleteHero have the following line:

var user = await this.GetActiveUser();

However, the content of GetActiveUser is:

private Task<AppUser> GetActiveUser()
{
    var userId = User.FindFirst(ClaimTypes.NameIdentifier)?.Value;
    var user = this._userRepository.GetUserByIdAsync(userId);

    return user;
}

The returned value will always be null, as the second line does not await the result from the _userRepository.

This may also be the source of your problem as another query is hitting the context before the second line in the above is complete.

Try:

var user = await this._userRepository.GetUserByIdAsync(userId);

You could also try changing:

return await _context.SaveChangesAsync() > 0;

to

return (await _context.SaveChangesAsync()) > 0;
Neil W
  • 7,670
  • 3
  • 28
  • 41
  • Hi! Unfortunately that did not solved the issue. Do you have maybe any other suggestions? – John Nov 23 '21 at 10:09
  • It gets broken when it tries to save. – John Nov 23 '21 at 10:22
  • Have added one other suggestion. Other than that, it's probably time to break you problem down into smaller chunks. Strip out most of the activity in a Controller method until you don't get the problem, then try adding bits back until you do. You'll then be able to get a much smaller reproducible problem that is easier to diagnose. – Neil W Nov 23 '21 at 10:37
  • Hi! I have deleted all the calls for saving the data from the inside of the repositories and used them only in the controller and that solved that issue! thank you so much for your help! – John Nov 23 '21 at 14:13
  • Good. And that's a more correct way to do it. Repositories should add and retrieve from a unit of work, but control of when that unit of work is committed, and execution of that, should be outside of the repositories. – Neil W Nov 23 '21 at 15:27
1

when you get this error ,make sure all awaitable methods has "await"

Basically, I get this error in these situations

hossein andarkhora
  • 740
  • 10
  • 23