метод c# Net Core выполняется несколько раз — создается несколько потоков

#ajax #asp.net-core #thread-safety #dbcontext #lifetime

Вопрос:

Контекст:

Из javascript я делаю вызов ajax контроллеру для обновления сущности «Владелец». Контроллер использует службу для обработки запроса и соответствующего обновления сущности. В методе обновления службы я также обновляю связанные данные «Владельца», а в некоторых случаях я также добавляю новую сущность «Владелец».

Проблема:

Здесь(в службе) метод обновления по какой-то причине вызывается несколько раз. Если посмотреть на список потоков, он показывает несколько активных рабочих потоков. Из — за этой проблемы я в конечном итоге добавляю несколько дублированных строк и обновляю противоречивые данные (некоторые поля отсутствуют-например, брюки и куртка). Я хочу, чтобы он запустился только один раз.

Если я выполню только операцию РЕДАКТИРОВАНИЯ, ничего плохого не произойдет.

Если я выполню операцию ДОБАВЛЕНИЯ, а затем операцию РЕДАКТИРОВАНИЯ, редактирование будет выполнено 2 раза, добавив еще 2 строки в базу данных.

Если я выполню 2 или более(назовем это число N) операций ДОБАВЛЕНИЯ, а затем и операции РЕДАКТИРОВАНИЯ, редактирование будет выполняться N x 2 раза, создавая N x 2 дублированных строки.

Я свел код к минимуму, насколько мог. Это немного длинновато, но я не хотел исключать ни одного случая, который действительно мог бы быть виновником.

Если предоставленного свернутого кода недостаточно, я также могу предоставить доступ к хранилищу для волшебников кода среди нас. Пожалуйста, помогите мне. Я действительно не знаю ничего другого, что я мог бы попытаться заставить это работать.

Используемый стек: .net core 2.2, последняя версия jquery, mysql, pomelo, ядро ef

Вещи, которые я уже пробовал:

  • асинхронность / ожидание
  • регистрация служб и репозиториев при запуске в области действия
  • регистрация служб и репозиториев при запуске как переходных
  • регистрация DbContext при запуске как переходный
  • ThreadPool.максимальный лимит(1,1)
  • js onClick / on («щелчок») / один («щелчок»)
  • all of the above combined

Frontend:

 <script>
     $(document).one("click", "#buttonId", function () {
     $(this).attr("disabled", "disabled");
     var result = validator.validate(validator);
     if (result.formIsValid) {
     $.ajax({
        type: "POST",
        url: "/controllerName/Edit",
        data: $('#editForm').serialize(),
        dataType: "json",
        success: function (response) {
            CloseModalById('editModal');
            ShowHeaderAlert(response, "success", 5000);
            $('#ownerListState').change();
        },
        error: function (error) {
            CloseModalById('editModal');
            swalErrorTimer(error.responseText, 7000);
        }
        });
       } else {
       updateUi(result.validationResults, "form-group", "error_span");
      }
      });
</script>
 

Controller:

     public OwnerController(IOwnerService ownerService,IMapper mapper)
    {
        _ownerService = ownerService;
        _mapper = mapper;
    }

    [HttpPost]
    [ValidateAntiForgeryToken]
    public  ActionResult EditModal(OwnerViewModel ownerVM)
    {
        try
        {
            var dto = _mapper.Map<OwnerDto> ownerVM);
            var result = _ownerService.UpdateOwner(dto);
            if (result.Successful)
            {
                return Ok(JsonConvert.SerializeObject("Successfully updated!"));
            }
            return BadRequest("Something went wrong!");
         }
         catch (Exception e)
         {
            Log.Error(e, "Something went wrong!");
            return BadRequest("Something went wrong!");
          }
    }
 

Service:

       public OwnerService(IOwnerRepository ownerRepository, ICatRepository catRepository, ICatService catService, IMapper mapper)
    {
        _ownerRepository = ownerRepository;
        _catRepository = catRepository;
        _catService = catService;
        _mapper = mapper;
    }

    public Result<OwnerDto> UpdateOwner(OwnerDto ownerDto)
            {
                try
                {
                    var oldOwner = ownerDto.Copy();

                    // case: changed status in other than AtWork =>  empty the position for this set
                    if (ownerDto.LocationId != null amp;amp; ownerDto.Status != "AtWork")
                    {
                        _catService.UpdateCatPosition(ownerDto.LocationId.Value, ownerDto.Biscuits, PositionEnum.InTheHouse);
                    }

                    // case: was AtWork, will be in raft => position changed
                    if (ownerDto.LocationId != null amp;amp; ownerDto.Status == "AtWork" amp;amp; ownerDto.LocationId != null amp;amp; ownerDto.Biscuits == ownerDto.OldNrOfBiscuits)
                    {
                        _catService.UpdateCatPosition(ownerDto.LocationId.Value, ownerDto.Biscuits, PositionEnum.InTheHouse);
                        _catService.UpdateCatPosition(ownerDto.LocationId.Value, ownerDto.Biscuits, PositionEnum.InTheGarden);
                    }

                    // case: no change for position but change for nr of biscuits => 
                    if (ownerDto.LocationId != null amp;amp; ownerDto.Status == "AtWork" amp;amp; ownerDto.Biscuits != ownerDto.OldNrOfBiscuits amp;amp; ownerDto.LocationId == null)
                    {
                        ownerDto.LocationId = ownerDto.LocationId;
                        // case we have added some new anv to this set => we have to also update the nr inside position
                        if (ownerDto.Biscuits > ownerDto.OldNrOfBiscuits)
                        {
                            var newNrBiscuits = ownerDto.Biscuits - ownerDto.OldNrOfBiscuits;
                            var catPosition = PositionEnum.InTheGarden;
                            _catService.UpdateCatPosition(ownerDto.LocationId.Value, newNrBiscuits, catPosition);
                        }
                        // case we have removed some biscuits from this owner => we have to also update the nr for the cat
                        if (ownerDto.Biscuits < ownerDto.OldNrOfBiscuits)
                        {
                            var newNrBiscuits = ownerDto.OldNrOfBiscuits - ownerDto.Biscuits;
                            var catPosition = PositionEnum.InTheHouse;
                            _catService.UpdateCatPosition(ownerDto.LocationId.Value, newNrBiscuits, catPosition);
                        }
                    }

                    // case: was AtWork, will be AtWork, Position changed, Biscuits changed

                    if (ownerDto.LocationId != null amp;amp; ownerDto.Status == "AtWork" amp;amp; ownerDto.LocationId != null amp;amp; ownerDto.LocationId != ownerDto.LocationId amp;amp; ownerDto.Biscuits != ownerDto.OldNrOfBiscuits)
                    {

                        var newNrBiscuits = ownerDto.OldNrOfBiscuits -(ownerDto.OldNrOfBiscuits - ownerDto.Biscuits);
                        var catPosition = PositionEnum.InTheGarden;
                        _catService.UpdateCatPosition(ownerDto.LocationId.Value, newNrBiscuits, catPosition);

                    }

                    // case: was not AtWork but will be AtWork
                    if (ownerDto.LocationId != null amp;amp; ownerDto.LocationId == null)
                    {
                        _catService.UpdateCatPosition(ownerDto.LocationId.Value, ownerDto.Biscuits, PositionEnum.InTheGarden);
                    }



                    if (ownerDto.LocationId != null)
                    {
                        var catPosition = catRepository.GetPositionById(ownerDto.LocationId.Value);

                        if (!catPosition.Successful)
                        {
                            Log.Error("Something went wrong!");
                            throw new Exception("Something went wrong!");
                        }

                        ownerDto.CatPosition = _mapper.Map<CatDto>(catPosition);
                    }


                    //------------ Pants

                    var pants = _ownerRepository.GetPants(ownerDto.Pants);
                    if (!pants.Successful)
                    {
                        Log.Error("log stuff here");
                        throw new Exception("Something went wrong!");
                    }

                    if (Pants == null amp;amp; !string.IsNullOrEmpty(ownerDto.Pants))
                    {
                        pants = _ownerRepository.AddPants(new DbModelPants() { Label = ownerDto.Pants.ToUpper() });
                    }

                    if (!pants.Successful)
                    {
                        Log.Error("log stuff here");
                        throw new Exception("Something went wrong!");   
                    }
                    ownerDto.pantsId = pants.Id;


                    ///////////////////// Jacket
                    var jacket = _ownerRepository.GetjacketByLabel(ownerDto.jacket);
                    if (!jacket.Successful)
                    {
                        Log.Error("log stuff here");
                        throw new Exception("Something went wrong!");     
                    }

                    if (jacket == null amp;amp; !string.IsNullOrEmpty(ownerDto.Jacket))
                    {
                        jacket = _ownerRepository.Addjacket(new DbModelJacket() { Label = ownerDto.jacket.ToUpper() });
                    }

                    if (!jacket.Successful)
                    {
                        Log.Error("log stuff here");
                        throw new Exception("Something went wrong!");         
                    }
                    ownerDto.JacketId = jacket.Id;

                    // ------------------------------------------------------------------------------------------

                    var modelForDatabase = _mapper.Map<OwnerDbModel>(ownerDto);
                    
                    modelForDatabase.LastModified = DateTime.Now;

                    var value = _ownerRepository.UpdateOwner(modelForDatabase);
                    if (!value.Successful)
                    {
                        return Result<OwnerDto>.ResultError(value.Error);
                    }
                    var returnModel = _mapper.Map<OwnerDto>(value);

                    // if we move the biscuits we need to create a new owner with the remaining biscuits values that were not moved to the new location
                    //--------- This adds a new Owner to the db with oldData
                    if (oldOwner.OldNrOfBiscuits > oldOwner.Biscuits amp;amp; oldOwner.LocationId != oldOwner.LocationId amp;amp; oldOwner.LocationId != null amp;amp; oldOwner.LocationId != null)
                    {
                        oldOwner.Biscuits = oldOwner.OldNrOfBiscuits - oldOwner.Biscuits;
                        oldOwner.LocationId = oldOwner.LocationId;
                       
                        oldOwner.Id = 0;

                        var addedOldSet = AddOwner(oldOwner, PositionEnum.AtThePetStore);

                        if (!addedOldSet.Successful)
                        {
                        Log.Error("log stuff here");
                        throw new Exception("Something went wrong!");       
                        }
                    }

                    return Result<OwnerDto>.ResultOk(returnModel);

                }
                catch (Exception er)
                {
                    Log.Error("log stuff here");
                    throw new Exception("Something went wrong!");      
                }
            }


     public Result<OwnerDto> AddOwner(OwnerDto owner, PositionEnum catPosition = PositionEnum.InTheGarden)
    {
        try
        {
            if (owner.LocationId != null amp;amp; owner.Status == "AtWork")
            {
                var catPosition = _catService.GetPositionById(owner.LocationId.Value);
                if (!catPosition.Successful)
                {
                    Log.Error("log stuff here");
                    throw new Exception("Something went wrong!");          
                }

                _catService.UpdatePosition(owner.LocationId.Value, owner.NrBucati, catPosition);
               
                catPosition = _catService.GetPositionById(owner.LocationId.Value);
                owner.CatPosition = catPosition;
            }
            //------------ Pants
            // if we don't find the pants with provided name in the table Pants, we add a new set of Pants and provide the Owner with that set. Same for the Jacket.
            var pants = _ownerRepository.GetPants(ownerDto.Pants);
            if (!pants.Successful)
            {
                Log.Error("log stuff here");
                throw new Exception("Something went wrong!");
            }

            if (Pants == null amp;amp; !string.IsNullOrEmpty(ownerDto.Pants))
            {
                pants = _ownerRepository.AddPants(new DbModelPants() { Label = ownerDto.Pants.ToUpper() });
            }

            if (!pants.Successful)
            {
                Log.Error("log stuff here");
                throw new Exception("Something went wrong!");   
            }
            ownerDto.PantsId = pants.Id;

            //----------- Jacket
            var jacket = _ownerRepository.GetjacketByLabel(ownerDto.Jacket);
            if (!jacket.Successful)
            {
                Log.Error("log stuff here");
                throw new Exception("Something went wrong!");     
            }

            if (jacket == null amp;amp; !string.IsNullOrEmpty(ownerDto.Jacket))
            {
                jacket = _ownerRepository.Addjacket(new DbModelJacket() { Label = ownerDto.jacket.ToUpper() });
            }

            if (!jacket.Successful)
            {
                Log.Error("log stuff here");
                throw new Exception("Something went wrong!");         
            }
            ownerDto.JacketId = jacket.Id;

            var modelForDatabase = _mapper.Map<OwnerDbModel>(owner);
            modelForDatabase.LastModified = DateTime.Now;
            modelForDatabase.Name = modelForDatabase.Name.ToUpper();

            // send model to database
            var addedOwner = _ownerRepository.AddOwnerToDatabase(modelForDatabase);
            var returnModel = _mapper.Map<OwnerDto>(addedOwner);
            return Result<OwnerDto>.ResultOk(returnModel);

        }
        catch (Exception er)
        {
            Log.Error("log stuff here");
            throw new Exception("Something went wrong!");         
        }
    }
 

Методы обновления и добавления во всех репозиториях аналогичны. Пример:

     public Entity UpdateEntity(Entity entityToUpdate)
    {
          _db.EntityDbSet.Update(entityToUpdate);
          _db.SaveChanges();
          return entityToUpdate;
    }
    
    public Entity AddEntity(Entity entityToAdd)
    {
          _db.EntityDbSet.Add(entityToAdd);
          _db.SaveChanges();
          return entityToAdd;
    }
 

Комментарии:

1. Потоки пула потоков не имеют никакого отношения к вашей проблеме; просто игнорируйте их. 1) Звоните только SaveChanges один раз на HTTP-запрос. 2) Почитайте немного о «оптимистичном параллелизме».

2. Спасибо @StephenCleary, я немного разберусь в этом.

Ответ №1:

Проблема была в HTML-файле. Частичное представление, содержащее модал, также загрузило файл js, и из-за этого оно загружало его несколько раз. Вот откуда поступили многочисленные звонки. Чтобы это сработало, я загрузил файл js в индекс. Такое простое решение сумасшедшей проблемы с сумасшедшими результатами…