You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何使用AutoMapper与EF Core 2更新对象集合?

问题:更新EF Core关联集合时触发实体跟踪冲突异常

我在尝试更新ProgramacionSemanal及其关联的ProgramacionSemanalDetalles集合时遇到了棘手的问题,保存更改时抛出了实体跟踪冲突的错误。以下是我的相关代码和错误信息:

实体模型

public class ProgramacionSemanal 
{ 
    public int ProgramacionSemanalId { get; set; } 
    public int Semana { get; set; } 
    public DateTime FechaInicio { get; set; } 
    public DateTime FechaFin { get; set; } 
    public ICollection<ProgramacionSemanalDetalle> ProgramacionSemanalDetalles { get; set; } 
} 

public class ProgramacionSemanalDetalle 
{ 
    [Key] 
    public int ProgramacionSemanalDetalleId { get; set; } 
    public int ProgramacionSemanalId { get; set; } 
    [Column("ClienteId")] 
    public Entidad Cliente { get; set; } 
    public int UbicacionId { get; set; } 
    public Ubicacion Ubicacion { get; set; } 
    public int ProductoId { get; set; } 
    public Producto Producto { get; set; } 
    public int Lunes { get; set; } 
    public int Martes { get; set; } 
    public int Miercoles { get; set; } 
    public int Jueves { get; set; } 
    public int Viernes { get; set; } 
    public int Sabado { get; set; } 
    public int Domingo { get; set; } 
    public AppUsuario Usuario { get; set; } 
}

API的PUT路由代码

[HttpPut("{programacionsemanalId}")] 
public async Task<IActionResult> Put(int programacionSemanalId, [FromBody] ProgramacionSemanalViewModel model) 
{ 
    try 
    { 
        var oldProgramacionSemanal = await _repository.GetProgramacionSemanalAsync(programacionSemanalId); 
        if (oldProgramacionSemanal == null) return NotFound($"No se encontro la Programación Semanal."); 

        _mapper.Map(model, oldProgramacionSemanal); 

        var currentUser = await _userManager.FindByNameAsync(User.Identity.Name); 

        foreach (var item in oldProgramacionSemanal.ProgramacionSemanalDetalles) 
        { 
            var producto = await _repository.GetProductoAsync(item.ProductoId); 
            if (producto == null) return BadRequest(); 
            item.Producto = producto; 

            var ubicacion = await _repository.GetUbicacionAsync(item.UbicacionId); 
            if (ubicacion == null) return BadRequest(); 
            item.Ubicacion = ubicacion; 

            item.Usuario = currentUser; 
        } 

        if (await _repository.SaveAllAsync()) 
        { 
            return Ok(_mapper.Map<ProgramacionSemanalViewModel>(oldProgramacionSemanal)); 
        } 
    } 
    catch (Exception ex) 
    { 
        _logger.LogError($"Threw exception while updating: {ex}"); 
    } 
    return BadRequest("Couldn't update"); 
}

抛出的错误信息

System.InvalidOperationException: The instance of entity type 'ProgramacionSemanalDetalle' cannot be tracked because another instance with the key value '{ProgramacionSemanalDetalleId: 38}' is already being tracked. When attaching existing entities, ensure that only one entity instance with a given key value is attached.
at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.IdentityMap1.ThrowIdentityConflict(InternalEntityEntry entry) at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.IdentityMap1.Add(TKey key, InternalEntityEntry entry, Boolean updateDuplicate)
at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.StateManager.StartTracking(InternalEntityEntry entry)
at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.InternalEntityEntry.SetEntityState(EntityState oldState, EntityState newState, Boolean acceptChanges)
at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.InternalEntityEntry.SetEntityState(EntityState entityState, Boolean acceptChanges, Nullable1 forceStateWhenUnknownKey) at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.EntityGraphAttacher.PaintAction(EntityEntryGraphNode node, Boolean force) at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.EntityEntryGraphIterator.TraverseGraph[TState](EntityEntryGraphNode node, TState state, Func3 handleNode)
at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.EntityGraphAttacher.AttachGraph(InternalEntityEntry rootEntry, EntityState entityState, Boolean forceStateWhenUnknownKey)
at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.NavigationFixer.NavigationCollectionChanged(InternalEntityEntry entry, INavigation navigation, IEnumerable1 added, IEnumerable1 removed)
at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.InternalEntityEntryNotifier.NavigationCollectionChanged(InternalEntityEntry entry, INavigation navigation, IEnumerable1 added, IEnumerable1 removed)
at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.ChangeDetector.DetectNavig


问题分析与解决方案

这个错误的核心是EF Core的实体跟踪机制在起作用:当你从数据库加载oldProgramacionSemanal时,它关联的ProgramacionSemanalDetalles集合已经被DbContext跟踪了。而AutoMapper的默认映射行为可能会用ViewModel中的集合完全替换原集合,导致新的ProgramacionSemanalDetalle实例(和旧实例主键相同)被添加到上下文中,同时旧实例还在被跟踪,引发冲突。另外,你手动获取Producto和Ubicacion的操作也可能引入重复跟踪的实体。

下面是几个针对性的解决方案:

1. 配置AutoMapper保留原集合,仅更新集合内实体

默认AutoMapper会替换整个集合,我们需要修改映射配置,让它复用原有的集合实例,只更新其中的实体属性:

// 在你的AutoMapperProfile配置类中添加
CreateMap<ProgramacionSemanalViewModel, ProgramacionSemanal>()
    // 告诉AutoMapper使用目标对象的现有集合,而不是替换它
    .ForMember(dest => dest.ProgramacionSemanalDetalles, opt => opt.UseDestinationValue())
    // 同时配置子实体的映射规则
    .ForMember(dest => dest.ProgramacionSemanalDetalles, opt => opt.MapFrom(src => src.ProgramacionSemanalDetalles));

// 还要配置子实体的ViewModel到实体的映射
CreateMap<ProgramacionSemanalDetalleViewModel, ProgramacionSemanalDetalle>();

这样AutoMapper会遍历原集合中的每个实体,用ViewModel对应的数据更新它们的属性,而不会创建新的实体实例,从根源上避免跟踪冲突。

2. 避免重复获取已跟踪的关联实体

当你加载oldProgramacionSemanal时,如果你的GetProgramacionSemanalAsync方法已经包含了Include(x => x.ProgramacionSemanalDetalles)(或者延迟加载已启用),那么集合中的ProgramacionSemanalDetalle已经被上下文跟踪,你不需要再手动获取Producto和Ubicacion——EF Core会自动维护这些关联的跟踪状态。你只需要检查ID是否存在,然后直接设置Usuario即可:

foreach (var item in oldProgramacionSemanal.ProgramacionSemanalDetalles)
{
    // 只检查实体是否存在,不获取实体实例(避免跟踪新实例)
    if (!await _repository.ExistsProductoAsync(item.ProductoId))
        return BadRequest($"Producto con ID {item.ProductoId} no existe");
    if (!await _repository.ExistsUbicacionAsync(item.UbicacionId))
        return BadRequest($"Ubicación con ID {item.UbicacionId} no existe");
    
    item.Usuario = currentUser;
}

如果你的仓储没有Exists方法,可以用Any查询:

if (!await _context.Productos.AnyAsync(p => p.ProductoId == item.ProductoId))
    return BadRequest(...);

3. 对查询的关联实体使用AsNoTracking

如果你确实需要获取Producto和Ubicacion,可以在查询时加上AsNoTracking(),这样DbContext不会跟踪这些实例,也就不会和已有的跟踪实例冲突:

var producto = await _repository.GetProductoAsync(item.ProductoId).AsNoTracking();
var ubicacion = await _repository.GetUbicacionAsync(item.UbicacionId).AsNoTracking();

注意:这种方式仅适用于你不需要修改Producto或Ubicacion实体的场景,如果你只是要设置关联,这完全可行。

4. 手动处理实体跟踪状态

如果以上方法都不适用,你可以手动检查上下文是否已经在跟踪某个实体,优先使用已跟踪的实例:

var producto = await _repository.GetProductoAsync(item.ProductoId);
if (producto == null) return BadRequest();

// 检查本地缓存中是否有已跟踪的Producto实例
var trackedProducto = _context.Productos.Local.FirstOrDefault(p => p.ProductoId == producto.ProductoId);
if (trackedProducto != null)
    item.Producto = trackedProducto; // 使用已跟踪的实例
else
    item.Producto = producto; // 附加新实例

// Ubicacion的处理逻辑同理

内容的提问来源于stack exchange,提问作者Alberto López

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.29 06:52:20