无法跟踪类型的实例,因为另一个实例具有相同的 ID 错误
instance of type cannot be tracked because of another instance with same ID error
我有一个带有 EF Core 的 .net core 2.1 mvc 应用程序,我在其中使用 automapper 将视图模型与域模型相匹配。在我的编辑方法中出现错误:
InvalidOperationException: The instance of entity type
'Ticket' cannot be tracked because another instance with the
same key value for {'ID'} is already being tracked.
这里的其他几个题目都没有解决我的问题
我的编辑方法:
[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 实体。
您正在加载域项目,但是您使用了错误的自动映射器调用:
ticketToUpdate = _mapper.Map<Ticket>(ticketViewModel);
这应该是:
_mapper.Map(ticketViewModel, ticketToUpdate);
第一种方法从视图模型中获取值并将它们加载到实体的全新实例中,并将其分配给之前加载的 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.
}
这里的 "newA" 表示对从 dbContext 中提取的实体的引用。我们对同一实体进行第二次引用以便稍后进行比较。 (altReferenceA)。如果我们调用 newA = mapper.Map<ClassA>(cloneB)
它现在是一个新的引用,这会导致 EF 出现异常。 EF 正在跟踪 altReferenceA 仍然指向的实体。 newA 被视为新的、未跟踪的实体。
在第二遍中,我们重置变量并使用 mapper.Map(cloneB, newA)
我们将值从 B 复制到 A,两个引用都被更新,因为它们仍然指向同一个对象。跟踪的实体已更新并可以保存。如果 B 的值没有写入 newA 那么我会怀疑从 B 到 A 的映射配置有问题。如果实体中的值正在更新但实体没有持久化更改,那么我会看看您的工作单元中的 Commit()
方法试图做什么。
我有一个带有 EF Core 的 .net core 2.1 mvc 应用程序,我在其中使用 automapper 将视图模型与域模型相匹配。在我的编辑方法中出现错误:
InvalidOperationException: The instance of entity type 'Ticket' cannot be tracked because another instance with the same key value for {'ID'} is already being tracked.
这里的其他几个题目都没有解决我的问题
我的编辑方法:
[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 实体。
您正在加载域项目,但是您使用了错误的自动映射器调用:
ticketToUpdate = _mapper.Map<Ticket>(ticketViewModel);
这应该是:
_mapper.Map(ticketViewModel, ticketToUpdate);
第一种方法从视图模型中获取值并将它们加载到实体的全新实例中,并将其分配给之前加载的 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.
}
这里的 "newA" 表示对从 dbContext 中提取的实体的引用。我们对同一实体进行第二次引用以便稍后进行比较。 (altReferenceA)。如果我们调用 newA = mapper.Map<ClassA>(cloneB)
它现在是一个新的引用,这会导致 EF 出现异常。 EF 正在跟踪 altReferenceA 仍然指向的实体。 newA 被视为新的、未跟踪的实体。
在第二遍中,我们重置变量并使用 mapper.Map(cloneB, newA)
我们将值从 B 复制到 A,两个引用都被更新,因为它们仍然指向同一个对象。跟踪的实体已更新并可以保存。如果 B 的值没有写入 newA 那么我会怀疑从 B 到 A 的映射配置有问题。如果实体中的值正在更新但实体没有持久化更改,那么我会看看您的工作单元中的 Commit()
方法试图做什么。