экземпляр типа не может быть отслежен из-за другого экземпляра с той же ошибкой идентификатора - PullRequest
0 голосов
/ 22 января 2020

У меня есть приложение. net core 2.1 mvc с EF Core, где я использую automapper для сопоставления моделей представления с моделями домена. В моем методе редактирования я получаю сообщение об ошибке:

InvalidOperationException: экземпляр типа сущности 'Ticket' не может быть отслежен, поскольку другой экземпляр с таким же значением ключа для {'ID'} уже отслеживается.

Несколько других тем не решили мою проблему.

Мой метод редактирования:

 [HttpPost]
 [ValidateAntiForgeryToken]
 public async Task<IActionResult> Edit(int id, TicketViewModel ticketViewModel)
    {
    Ticket ticketToUpdate = await _unitOfWork.Ticket.Get(id); // I implement unit of work 

    // some logic for checks
    // ...

        if (ModelState.IsValid)
        {
            try
            {
                // mapping  Ticket viewmodel to Ticket Domain Model
                ticketViewModel = _mapper.Map<TicketViewModel>(ticketToUpdate);

                // update some properties on viewmodel

                _mapper.Map(ticketViewModel, ticketToUpdate); // doesn't map values.

                _unitOfWork.Ticket.Update(ticketToUpdate); //No longer fails
                await _unitOfWork.Commit();
            }
            catch (DbUpdateConcurrencyException)
            {
                return NotFound();
            }
        return RedirectToAction(nameof(Index));
    }

Мое сопоставление:

CreateMap<TicketViewModel, Ticket>()
.ForMember(x => x.ID, x => x.MapFrom(y => y.Ticket.ID))
.ForMember(x => x.Title, x => x.MapFrom(y => y.Ticket.Title))
.ForMember(x => x.Description, x => x.MapFrom(y => y.Ticket.Description))


CreateMap<Ticket, TicketViewModel>()
.ForPath(x => x.Ticket.ID, x => x.MapFrom(y => y.ID))
.ForPath(x => x.Ticket.Title, x => x.MapFrom(y => y.Title))
.ForPath(x => x.Ticket.Description, x => x.MapFrom(y => y.Description))

РЕДАКТИРОВАТЬ: InvalidOperationException теперь решена, но окончательное сопоставление, по-видимому, не отображает значения модели представления в сущность _dbcontext.

1 Ответ

1 голос
/ 23 января 2020

Вы загружаете элемент домена, однако вы используете неправильный вызов автоматического обработчика:

ticketToUpdate = _mapper.Map<Ticket>(ticketViewModel);

Это должно быть:

_mapper.Map(ticketViewModel, ticketToUpdate);

Первый метод получает значения из представления моделирует и загружает их в совершенно новый экземпляр сущности и назначает его ранее загруженной ссылке ticketToUpdate. Когда вы go обновляете эту ссылку, dbContext за вашей единицей работы уже отслеживает сущность с тем же идентификатором, поэтому вы получаете ошибку. (Обновленная ссылка обрабатывается как новая сущность)

Второй пример вызова Map копирует значения из ViewModel в сущность, на которую ссылается ticketToUpdate. Результирующая ссылка указывает на исходную сущность, которая получает новые значения, и DbContext сохранит эти изменения.

** Редактирование: простой тест для определения различий в поведении с помощью вызова Map. Если вызов Map(source, destination) не копирует ожидаемые значения, проверьте сопоставления, чтобы убедиться в правильности двустороннего преобразования.

[Test]
public void TestCopyOver()
{
    var config = new MapperConfiguration(cfg =>
    {
        cfg.CreateMap<ClassA, ClassB>()
            .ForMember(x => x.MyName, x => x.MapFrom(y => y.Name))
            .ForMember(x => x.MyOtherName, x => x.MapFrom(y => y.OtherName));
        cfg.CreateMap<ClassB, ClassA>()
            .ForMember(x => x.Name, x => x.MapFrom(y => y.MyName))
            .ForMember(x => x.OtherName, x => x.MapFrom(y => y.MyOtherName));
    });

    var mapper = config.CreateMapper();

    ClassA newA = new ClassA { Name = "Fred", OtherName = "Astaire" };
    ClassA altReferenceA = newA;
    Assert.AreSame(newA, altReferenceA, "References don't match.");
    var cloneB = mapper.Map<ClassB>(newA);
    cloneB.MyOtherName = "Rogers";

    newA = mapper.Map<ClassA>(cloneB);
    Assert.AreEqual("Rogers", newA.OtherName);
    Assert.AreEqual("Astaire", altReferenceA.OtherName); // original object not updated.
    Assert.AreNotSame(newA, altReferenceA); // now point to 2 different objects

    //Reset...

    newA = new ClassA { Name = "Fred", OtherName = "Astaire" };
    altReferenceA = newA;
    Assert.AreSame(newA, altReferenceA, "References don't match.");
    cloneB = mapper.Map<ClassB>(newA);
    cloneB.MyOtherName = "Rogers";

    mapper.Map(cloneB, newA);
    Assert.AreEqual("Rogers", newA.OtherName);
    Assert.AreEqual("Rogers", altReferenceA.OtherName); // Original object updated.
    Assert.AreSame(newA, altReferenceA); // Still point to same reference.
}

Здесь «newA» представляет ссылку на объект, извлеченный из DBContext. Мы возьмем вторую ссылку на эту же сущность, чтобы сравнить позже. (AltReferenceA). Если мы называем newA = mapper.Map<ClassA>(cloneB), то теперь это новая ссылка, и это приводит к исключению с EF. EF отслеживает сущность, на которую все еще указывает altReferenceA. newA рассматривается как новая неотслеживаемая сущность.

Во втором проходе мы сбрасываем переменные и, используя mapper.Map(cloneB, newA), копируем значение из B в A, обе ссылки обновляются, поскольку они все еще указывают на один и тот же объект , Отслеживаемый объект обновляется и может быть сохранен. Если значения из B не записываются в newA, то я подозреваю, что что-то не так с конфигурацией отображения из B в A. Если значения обновляются в сущности, но сущность не сохраняет изменения, то я бы посмотрел на что пытается сделать метод Commit() в вашей единице работы.

...