.Net Core DBContext 依赖注入

.Net Core DBContext dependency injection

我已经根据 MS Instructions 和本网站上的其他帖子重构了我的代码,以避免出现以下错误,它似乎略有改进,但是 如果我有多个按钮快速连续点击 我仍然收到以下错误:

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")));

在我的控制器中:

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");
}

await RGWIncrementClicks.UserClick(actionNumber, userId, _context);被触发时,UserClick方法(未显示)有一个switch语句,它可以调用其他方法的组合以根据actionNumber向DB添加值参数传入。

我尝试了使用 await 的所有后续方法和调用,但仍然出现多线程错误。我也尝试在没有他们的情况下使用 await,但仍然出现多线程错误。

我不明白的是,await RGWIncrementClicks.UserClick(actionNumber, userId, _context);是所有使用dbContext的后续函数的入口点,它使用await,加上它为此传递了注入的dbContext调用实例,那么我怎么会在后续函数中出现线程错误...它们应该只在此处等待入口点完成后触发第二次,表明所有后续函数都已完成...不是吗?

更新: 我只是在想我实际上并不希望它是异步的,因为我希望 UI 在每次用户点击时更新RedirectToAction 在数据库更改后触发。所以也许这不是线程管理问题,而是用户管理问题....我应该防止用户在触发我的 RGWInsertNewActionNumber 方法的任何按钮上单击两次...

Update.2 - 为了保存 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;
            }
        }
    }
}

错误详情:

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()

尝试替换

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

有非静态的

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

我决定使用 javascript 避免在 UI 图层上多次单击。我对此有一个小问题,但已解决。如果其他人需要防止多次 UI 点击,请在此处获取更多信息。