由于另一个具有相同 ID 错误的实例,无法跟踪该类型的实例

CMo*_*gan 1 c# entity-framework invalidoperationexception automapper asp.net-core-mvc

我有一个带有 EF Core 的 .net core 2.1 mvc 应用程序,其中我使用自动映射器将视图模型与域模型进行匹配。在我的编辑方法中,我收到错误:

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));
    }
Run Code Online (Sandbox Code Playgroud)

我的映射:

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))
Run Code Online (Sandbox Code Playgroud)

编辑: InvalidOperationException 现已解决,但最终映射似乎并未将 viewmodel 的值映射到 _dbcontext 实体。

Ste*_* Py 7

您正在加载域项目,但是您使用了错误的自动映射器调用:

ticketToUpdate = _mapper.Map<Ticket>(ticketViewModel);
Run Code Online (Sandbox Code Playgroud)

这应该是:

_mapper.Map(ticketViewModel, ticketToUpdate);
Run Code Online (Sandbox Code Playgroud)

第一个方法从视图模型中获取值并将它们加载到实体的全新实例中,并将其分配给之前加载的ticketToUpdate引用。当您去更新该引用时,工作单元背后的 dbContext 已经在跟踪具有相同 ID 的实体,因此您会收到错误。(更新后的引用被视为新实体)

第二个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.
}
Run Code Online (Sandbox Code Playgroud)

这里的“newA”表示对从 dbContext 中提取的实体的引用。我们对同一实体进行第二次引用以便稍后进行比较。(替代参考文献A)。如果我们现在调用newA = mapper.Map<ClassA>(cloneB)它是一个新引用,这会导致 EF 异常。EF 正在跟踪 altReferenceA 仍指向的实体。newA 被视为一个新的、未跟踪的实体。

在第二遍中,我们重置变量并使用mapper.Map(cloneB, newA)我们将值从 B 复制到 A,两个引用都会更新,因为它们仍然指向同一个对象。跟踪的实体已更新并可以保存。如果 B 中的值没有写入 newA,那么我会怀疑从 B 到 A 的映射配置有问题。如果实体中的值正在更新,但实体没有保留更改,那么我会查看Commit()您的工作单元中的方法正在尝试执行什么操作。