Простое веб-приложение с ASP.NET MVC + EF Core иногда создает несколько идентичных записей в базе данных. - PullRequest
0 голосов
/ 25 сентября 2019

У меня есть веб-приложение, в котором пользователи могут публиковать предложения для продуктов.

Иногда - менее 1 в 10 случаях - сохранение предложения приводит к нескольким записям в базе данных с идентичными данными (обычно 2 записи, но3 или даже 4 идентичных записи уже произошли).

Класс предложения - это простой класс сущности с некоторыми свойствами в нем.Он также связан с двумя другими классами сущностей 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 добавляются в области видимости (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 - ВСТАВИТЬ INTO ПРЕДЛОЖЕНИЕ ...
  2. SPID 57 - 2019-09-24 16:05: 19.673 - INSERT INTO Offer ...
  3. SPID 59 - 2019-09-24 16: 05: 19.710 - INSERT INTO OfferImage ... / INSERT INTO OfferCategory ...
  4. SPID 57- 2019-09-24 16: 05: 19.760 - INSERT INTO OfferImage ... / INSERT INTO OfferCategory ...

Что вызывает у меня подозрение, так это два разных идентификатора процесса, которые выполняют INSERT,Поскольку DbContext ограничен по умолчанию, должен быть только один идентификатор процесса, под которым выполняются все государственные деятели - или я ошибаюсь?Если я не ошибаюсь, это будет означать, что два запроса выполняются параллельно, что, в свою очередь, вызывает дополнительные вопросы о том, как это может произойти.

Как видите, я немного растерялся и надеюсь на помощькто-то, кто может объяснить это или наблюдал и решил что-то подобное.

(SQL Server - версия 13/2016, EF Core - версия 2.2)

Большое спасибо!

1 Ответ

1 голос
/ 25 сентября 2019

Каждый вызов вашего API может использовать другой идентификатор потока.Если PID действительно другой процесс, то у вас одновременно работают 2 разных экземпляра вашего API.

Если вам действительно не нужны дубликаты, добавьте ограничение в базу данных, чтобы избежать дубликатов (название продукта или что-то еще).Это означает, что SaveChanges сгенерирует DbUpdateException, который вам нужно будет отловить и определить, является ли это дублирующим исключением (которое вы можете отправить пользователю обратно через код ответа HTTP, возможно конфликт 409) или что-то еще(что, вероятно, ошибка 5хх).

...