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 实体。
您正在加载域项目,但是您使用了错误的自动映射器调用:
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()您的工作单元中的方法正在尝试执行什么操作。