DMur
DMur

Reputation: 653

.Net Core DBContext dependency injection

I have refactored my code to try to avoid getting the error below as per the MS Instructions and other posts on this site, it seems to have improved slightly, but if I have multiple button clicks in rapid succession I am still getting the error below:

A second operation started on this context before a previous operation completed. This is usually caused by different threads using the same instance of DbContext, however instance members are not guaranteed to be thread safe.

startup.cs

services.AddDbContext<ApplicationDbContext>(options => options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));

In My Controller:

readonly RGWIncrementUserActionClicks RGWIncrementClicks = new();
private readonly ApplicationDbContext _context;

public RGWController(IEmailSender emailSender, ApplicationDbContext context)
{
_emailSender = emailSender;
_context = context;
}

public async Task <IActionResult> RGWInsertNewActionNumber(int actionNumber)
{
string userId = ClaimsPrincipalExtensions.GetUserId<string>(User);

await RGWIncrementClicks.UserClick(actionNumber, userId, _context);

return RedirectToAction("RGW");
}

When await RGWIncrementClicks.UserClick(actionNumber, userId, _context); is triggered, the UserClick method (Not shown) has a switch statement which can call combinations of other methods to add values to the DB based on the actionNumber parameter passed in.

I tried with all subsequent methods and calls using await, but still get the multiple thread error. I also tried without them using await and still get the multiple thread error.

What I don't understand is that await RGWIncrementClicks.UserClick(actionNumber, userId, _context); is the point of entry to all subsequent functions that use dbContext, and it uses await, plus it is passing in the injected dbContext for this call instance, so how am I getting a threading error in subsequent functions...they should only trigger a second time after the await here for the entry point completes, indicating all subsequent functions are finished...no?

Update: I'm just thinking that I don't actually want this to be async, because I want the UI to update on each user click when the RedirectToAction triggers after the Database has been changed. So perhaps this is not a thread management issue, instead its a user management issue....I should prevent the user from clicking twice on any button that triggers my RGWInsertNewActionNumber method...

Update.2 - I had left this out to save space:

public class RGWIncrementUserActionClicks
{
private static readonly IncrementSiteActions _IncrementSiteActions = new();
private static readonly IncrementUserActions _IncrementUserActions = new();

public async Task UserClick(int actionNumber, string userId, ApplicationDbContext _context)
        {
var userActions = await _context.UserEnvironmentalActionCounts.FindAsync(userId);
var siteTotalActions = await _context.SiteEnvironmentalActionCounts.FirstOrDefaultAsync();

siteTotalActions.SiteGlobalWarmingClicks++;

switch (actionNumber)
{
case 1:

if (userActions != null)
{
await _IncrementUserActions.incrementUserActions(userId, userActions => { userActions.ReduceMeat++; userActions.UserReduceMeatCO2Total = userActions.UserReduceMeatCO2Total + 2.0075; }, userActions => { userActions.UserCO2Total = userActions.UserCO2Total + 2.0075; });

if (siteTotalActions != null)
{
await _IncrementSiteActions.incrementSiteActions(siteTotalActions => { siteTotalActions.SiteReduceMeat++; }, true, true, true);
}
else
{
await _context.SiteEnvironmentalActionCounts.AddAsync(new SiteEnvironmentalActionCounts { SiteTotal = 1, SiteGlobalWarmingTotal = 1, SiteReduceMeat = 1, SiteDeforestationTotal = 1, SiteExtinctionTotal = 1 });
}
}
else
{
await _context.UserEnvironmentalActionCounts.AddAsync(new UserEnvironmentalActionCounts { Id = userId, ReduceMeat = 1, UserTotal = 1, UserReduceMeatCO2Total = 2.0075, UserCO2Total = 2.0075 });

if (siteTotalActions != null)
{
await _IncrementSiteActions.incrementSiteActions(siteTotalActions => { siteTotalActions.SiteReduceMeat++; }, true, true, true);
}
else
{
await _context.SiteEnvironmentalActionCounts.AddAsync(new SiteEnvironmentalActionCounts { SiteTotal = 1, SiteGlobalWarmingTotal = 1, SiteReduceMeat = 1, SiteDeforestationTotal = 1, SiteExtinctionTotal = 1 });
}
}
await _context.SaveChangesAsync();
break;

case 2:

...etc.

default:
break;
            }
        }
    }
}

Error details:

Microsoft.EntityFrameworkCore.DbContext.SaveChangesAsync(bool acceptAllChangesOnSuccess, CancellationToken cancellationToken)
GatheringForGood.Areas.FunctionalLogic.IncrementUserActions.incrementUserActions(string userId, Action<UserEnvironmentalActionCounts> update, Action<UserEnvironmentalActionCounts> updateUserCO2Total) in IncrementUserActions.cs
+
23. await _context.SaveChangesAsync();
GatheringForGood.Areas.FunctionalLogic.RGWIncrementUserActionClicks.UserClick(int actionNumber, string userId, ApplicationDbContext _context) in RGWIncrementUserActionClicks.cs
+
30. await _IncrementUserActions.incrementUserActions(userId, userActions => { userActions.ReduceMeat++; userActions.UserReduceMeatCO2Total = userActions.UserReduceMeatCO2Total + 2.0075; }, userActions => { userActions.UserCO2Total = userActions.UserCO2Total + 2.0075; });
MyApp.Controllers.ReduceGlobalWarmingController.RGWInsertNewActionNumber(int actionNumber) in ReduceGlobalWarmingController.cs
+
            354. await RGWIncrementClicks.UserClick(actionNumber, userId, _context);
Microsoft.AspNetCore.Mvc.Infrastructure.ActionMethodExecutor+TaskOfIActionResultExecutor.Execute(IActionResultTypeMapper mapper, ObjectMethodExecutor executor, object controller, object[] arguments)
System.Threading.Tasks.ValueTask<TResult>.get_Result()

Upvotes: 0

Views: 619

Answers (2)

DMur
DMur

Reputation: 653

I have decided to avoid multiple clicks on the UI layer using javascript. I had a small issue with this, but it was resolved. More info here if anyone else needs to prevent multiple UI clicks.

JS function for a number of elements onclick without using getElementById

Upvotes: 0

Serge
Serge

Reputation: 43959

try to replace

private static readonly IncrementSiteActions _IncrementSiteActions = ....
private static readonly IncrementUserActions _IncrementUserActions = ....

with non static ones

private  readonly IncrementSiteActions _IncrementSiteActions = ....
private  readonly IncrementUserActions _IncrementUserActions = ....

Upvotes: 1

Related Questions