【问题标题】:.Net Core DBContext dependency injection.Net Core DBContext 依赖注入
【发布时间】:2021-10-23 08:11:24
【问题描述】:

我已经重构了我的代码,以尽量避免根据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 RGWIncrementClicks.UserClick(actionNumber, userId, _context);是所有使用dbContext的后续函数的入口点,它使用await,加上它为这个调用实例传入了注入的dbContext,所以我如何我在后续函数中遇到线程错误...它们应该只在此处等待入口点完成后触发第二次,表明所有后续函数都已完成...不是吗?

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

Update.2 - 为了节省空间,我把它省略了:

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

【问题讨论】:

  • 你必须至少显示 RGWIncrementClicks.UserClick。
  • 谢谢,现在更新@Serge

标签: c# asp.net thread-safety dbcontext


【解决方案1】:

尝试替换

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

非静态的

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

【讨论】:

    【解决方案2】:

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

    JS function for a number of elements onclick without using getElementById

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2016-05-22
      • 1970-01-01
      • 2020-09-12
      • 2020-10-06
      • 1970-01-01
      • 2021-02-02
      • 1970-01-01
      • 2020-12-20
      相关资源
      最近更新 更多