【问题标题】:Simple web application with ASP.NET MVC + EF Core sometimes creates several identical entries in the database使用 ASP.NET MVC + EF Core 的简单 Web 应用程序有时会在数据库中创建多个相同的条目
【发布时间】:2020-01-25 20:08:24
【问题描述】:

我有一个网络应用程序,用户可以在其中发布产品报价。

有时 - 少于十分之一的情况 - 保存报价会导致数据库中的多个条目具有相同的数据(通常是 2 个条目,但已经出现 3 个甚至 4 个相同的条目)。

offer 类是一个简单的实体类,其中包含一些属性。它还连接到另外两个实体类 OfferImage 和 OfferCategory,它们存储关联的图像和商品应出现的类别。

将项目保存到数据库的代码如下(存储库类的一部分):

public class OfferRepository {
    ...
    public async Task InsertAsync(Offer offer)
    {
        Context.Offer.Add(offer);
        await Context.SaveChangesAsync();
    }
    ...
}

在服务类中调用:

public class OfferService 
{
    ...
    public async Task CheckinAsync(Offer offer)
    {
        await repository.InsertAsync(offer);
    }
    ...
}

这个服务类的那个方法被一个mvc控制器调用:

public async Task<IActionResult> Create(CreateOfferViewModel createOfferViewModel)
{
    if (ModelState.IsValid)
    {
        ...
        //conversion of the view model object to an Offer object
        ...
        await offerService.CheckinAsync(offer);
    }
    ...
}

如您所见,结构相对简单。但是,此错误经常发生。

服务类的生命周期通过 startup.cs 中的依赖注入进行管理。 OfferRepository 和 OfferService 都添加了 scoped (services.AddScoped) 上下文类是这样添加的:

services.AddDbContext<Context>(options =>
{
    options.UseSqlServer(
        Configuration.GetConnectionString("DataConnection"),
        sqlServerOptionsAction: sqlServerOptions =>
            {
                sqlServerOptions.EnableRetryOnFailure(maxRetryCount: 3, maxRetryDelay: TimeSpan.FromSeconds(3), errorNumbersToAdd: null);
            }
    );
});

为了进一步缩小问题范围,我运行了一个分析器并记录了 INSERT 语句(按时间顺序):

  1. SPID 59 - 2019-09-24 16:05:19.670 - 插入报价 ...
  2. SPID 57 - 2019-09-24 16:05:19.673 - 插入报价 ...
  3. SPID 59 - 2019-09-24 16:05:19.710 - 插入 OfferImage .../插入 OfferCategory ...
  4. SPID 57 - 2019-09-24 16:05:19.760 - 插入 OfferImage .../插入 OfferCategory ...

让我怀疑的是,执行 INSERT 有两个不同的进程 ID。由于默认情况下 DbContext 是作用域的,因此应该只有一个进程 ID,所有语句都在该进程 ID 下执行 - 还是我错了?如果我没记错的话,这意味着两个请求是并行执行的,这反过来又引发了关于如何发生这种情况的进一步问题。

如您所见,我有点困惑,希望能得到可以解释这一点或观察并解决过类似问题的人的帮助。

(SQL Server 是 13/2016 版,EF Core 是 2.2 版)

非常感谢!

【问题讨论】:

  • 快速提问,您确定端点没有被客户端调用两次吗?
  • 感谢您的提示。也是我最初的想法之一,所以我测试了它。但是多次点击网站上的“保存”按钮不会导致多次输入。以及多个请求,例如对于我们的用户和发生这种情况的情况,可以排除 Fiddler。在测试过程中,我什至能够自己观察到这种行为,而且我非常确定我只触发了一个请求。
  • 您也可以尝试使用 Postman 将一个请求发送到您的端点。另一个问题,Offer 中是否有什么独特之处可以确保它被添加一次?然后你可以用它来做一个 AddOrUpdate ..
  • 您如何管理 Context(以及服务和 repo)的生命周期?你在使用依赖注入吗?如果是这样,您正在使用什么生命周期(瞬态、范围等)。如果在不同的请求中使用相同的上下文,这可能是一个潜在的原因。
  • 感谢您指出这一点。我将尝试用 Postman 重现问题并进一步调查。我还将 Insert() 更改为 Update() (也可以使用它,因为 Offer 附加了一个自动键)。但不幸的是,问题并没有解决。但即使它已经解决了,我也想知道发生了什么。

标签: c# sql-server asp.net-core asp.net-core-mvc entity-framework-core


【解决方案1】:

对您的 API 的每次调用都可以使用不同的线程 ID。如果 PID 确实是一个不同的进程,那么您有 2 个不同的 API 实例同时运行。

如果您真的不希望重复,则向您的数据库添加一个约束以防止重复(产品名称或其他内容)。这意味着SaveChanges 将抛出一个DbUpdateException,您需要捕获并确定它是否是重复异常(您可以通过HTTP 响应代码将错误发送回用户,可能是409 冲突)或其他(这可能是一个 5xx 错误)。

【讨论】:

  • 感谢您提供有关限制的信息和提示!无论如何,我应该这样做,我想。但是,这感觉更像是解决绝对不应该发生的问题的解决方法。至少,我想了解发生了什么。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-08-12
  • 1970-01-01
  • 2020-04-01
  • 1970-01-01
相关资源
最近更新 更多