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

EF Core 7更新数据时出现SiteFrame跟踪冲突错误求助

EF Core 7更新关联实体时的跟踪冲突错误解决

场景与代码

使用.NET 7 + Entity Framework Core 7,数据库为PostgreSQL,上下文注册为Scoped,通过AutoMapper完成DTO到实体的映射。

更新方法代码

public async Task UpdateAsync(int id, SiteDto dto)
{
    Site site = await _context.Sites.Where(x => x.Id == id)
        .Include(x => x.Network)
        .Include(x => x.Centre)
        .Include(x => x.SiteDayParts).ThenInclude(x => x.SiteFrames)
        .Include(x => x.Resolution)
        .SingleOrDefaultAsync();

    _mapper.Map(dto, site);

    await _context.SaveChangesAsync();
}

DTO类定义

public class SiteDto
{
    public string Name { get; set; }
    public string Address { get; set; }
    public MediaFormat Formats { get; set; }
    public int ResolutionId { get; set; }
    public List<SiteDayPartDto> SiteDayParts { get; set; }
}

public class SiteDayPartDto
{
    public int Id { get; set; }
    public TimeSpan StartTime { get; set; }
    public TimeSpan EndTime { get; set; }
    public int Duration { get; set; }
    public List<SiteFrameDto> SiteFrames { get; set; }
}

public class SiteFrameDto
{
    public int Id { get; set; }
    public int FrameId { get; set; }
}

AutoMapper映射配置

CreateMap<SiteDto, Site>();

CreateMap<SiteDayPartDto, SiteDayPart>()
      .ForMember(dest => dest.AdLength, opt => opt.MapFrom(x => x.Duration));      

CreateMap<SiteFrameDto, SiteFrame>();

错误信息

The instance of entity type 'SiteFrame' cannot be tracked because another instance with the key value '{Id: 1}' is already being tracked. When attaching existing entities, ensure that only one entity instance with a given key value is attached.

注:相同逻辑在Entity Framework 6中可正常运行。

问题原因

EF Core与EF6的实体跟踪机制存在差异:当通过Include加载SiteFrames后,这些实体已被上下文跟踪。而AutoMapper默认会直接替换实体集合(如SiteFrames),或在映射时创建新的SiteFrame实例(即使DTO包含已存在的Id),导致同一个主键的实体出现两个被跟踪的实例,触发冲突。

解决方案

方案1:手动处理关联集合的增删改

放弃AutoMapper自动映射集合,手动遍历DTO集合,匹配已有实体进行属性更新,同时处理新增和删除:

public async Task UpdateAsync(int id, SiteDto dto)
{
    Site site = await _context.Sites.Where(x => x.Id == id)
        .Include(x => x.Network)
        .Include(x => x.Centre)
        .Include(x => x.SiteDayParts).ThenInclude(x => x.SiteFrames)
        .Include(x => x.Resolution)
        .SingleOrDefaultAsync();

    // 映射Site的基础属性
    _mapper.Map(dto, site);

    // 处理SiteDayParts
    foreach (var dayPartDto in dto.SiteDayParts)
    {
        var existingDayPart = site.SiteDayParts.FirstOrDefault(dp => dp.Id == dayPartDto.Id);
        if (existingDayPart != null)
        {
            // 映射SiteDayPart的基础属性
            _mapper.Map(dayPartDto, existingDayPart);

            // 移除不在DTO中的SiteFrame
            var framesToRemove = existingDayPart.SiteFrames
                .Where(f => !dayPartDto.SiteFrames.Any(dtoF => dtoF.Id == f.Id))
                .ToList();
            foreach (var frame in framesToRemove)
            {
                existingDayPart.SiteFrames.Remove(frame);
                _context.SiteFrames.Remove(frame);
            }

            // 更新或新增SiteFrame
            foreach (var frameDto in dayPartDto.SiteFrames)
            {
                var existingFrame = existingDayPart.SiteFrames.FirstOrDefault(f => f.Id == frameDto.Id);
                if (existingFrame != null)
                {
                    _mapper.Map(frameDto, existingFrame);
                }
                else
                {
                    var newFrame = _mapper.Map<SiteFrame>(frameDto);
                    existingDayPart.SiteFrames.Add(newFrame);
                }
            }
        }
        else
        {
            // 新增SiteDayPart及其关联SiteFrame
            var newDayPart = _mapper.Map<SiteDayPart>(dayPartDto);
            site.SiteDayParts.Add(newDayPart);
        }
    }

    // 移除不在DTO中的SiteDayPart
    var dayPartsToRemove = site.SiteDayParts
        .Where(dp => !dto.SiteDayParts.Any(dtoDp => dtoDp.Id == dp.Id))
        .ToList();
    foreach (var dayPart in dayPartsToRemove)
    {
        site.SiteDayParts.Remove(dayPart);
        _context.SiteDayParts.Remove(dayPart);
    }

    await _context.SaveChangesAsync();
}

方案2:配置AutoMapper不替换集合,映射现有元素

修改AutoMapper配置,针对集合类型,指定使用已跟踪的实体进行映射,而非创建新实例:

CreateMap<SiteDto, Site>()
    .ForMember(dest => dest.SiteDayParts, opt => opt.MapFrom((src, dest) => 
        dest?.SiteDayParts != null 
            ? MapSiteDayParts(src.SiteDayParts, dest.SiteDayParts)
            : src.SiteDayParts.Select(dto => _mapper.Map<SiteDayPart>(dto))
    ));

CreateMap<SiteDayPartDto, SiteDayPart>()
    .ForMember(dest => dest.AdLength, opt => opt.MapFrom(x => x.Duration))
    .ForMember(dest => dest.SiteFrames, opt => opt.MapFrom((src, dest) => 
        dest?.SiteFrames != null 
            ? MapSiteFrames(src.SiteFrames, dest.SiteFrames)
            : src.SiteFrames.Select(dto => _mapper.Map<SiteFrame>(dto))
    ));

CreateMap<SiteFrameDto, SiteFrame>();

// 辅助方法:映射SiteDayPart集合,复用现有实体
private List<SiteDayPart> MapSiteDayParts(List<SiteDayPartDto> dtos, List<SiteDayPart> existingParts)
{
    foreach (var dto in dtos)
    {
        var existingPart = existingParts.FirstOrDefault(p => p.Id == dto.Id);
        if (existingPart != null)
        {
            _mapper.Map(dto, existingPart);
        }
        else
        {
            existingParts.Add(_mapper.Map<SiteDayPart>(dto));
        }
    }

    // 移除不在DTO中的元素
    var partsToRemove = existingParts.Where(p => !dtos.Any(dto => dto.Id == p.Id)).ToList();
    foreach (var part in partsToRemove)
    {
        existingParts.Remove(part);
    }

    return existingParts;
}

// 辅助方法:映射SiteFrame集合,复用现有实体
private List<SiteFrame> MapSiteFrames(List<SiteFrameDto> dtos, List<SiteFrame> existingFrames)
{
    foreach (var dto in dtos)
    {
        var existingFrame = existingFrames.FirstOrDefault(f => f.Id == dto.Id);
        if (existingFrame != null)
        {
            _mapper.Map(dto, existingFrame);
        }
        else
        {
            existingFrames.Add(_mapper.Map<SiteFrame>(dto));
        }
    }

    // 移除不在DTO中的元素
    var framesToRemove = existingFrames.Where(f => !dtos.Any(dto => dto.Id == f.Id)).ToList();
    foreach (var frame in framesToRemove)
    {
        existingFrames.Remove(frame);
    }

    return existingFrames;
}

方案3:关闭查询跟踪,手动管理实体状态

如果不需要保留原查询的跟踪,可以用AsNoTracking()加载实体,之后手动设置实体状态为Modified,并处理关联实体的状态:

public async Task UpdateAsync(int id, SiteDto dto)
{
    Site site = await _context.Sites.Where(x => x.Id == id)
        .Include(x => x.Network)
        .Include(x => x.Centre)
        .Include(x => x.SiteDayParts).ThenInclude(x => x.SiteFrames)
        .Include(x => x.Resolution)
        .AsNoTracking() // 关闭跟踪
        .SingleOrDefaultAsync();

    _mapper.Map(dto, site);

    // 手动设置Site状态为修改
    _context.Sites.Update(site);

    // 手动处理关联实体状态(如需)
    foreach (var dayPart in site.SiteDayParts)
    {
        if (dayPart.Id > 0)
        {
            _context.SiteDayParts.Update(dayPart);
            foreach (var frame in dayPart.SiteFrames)
            {
                if (frame.Id > 0)
                {
                    _context.SiteFrames.Update(frame);
                }
                else
                {
                    _context.SiteFrames.Add(frame);
                }
            }
        }
        else
        {
            _context.SiteDayParts.Add(dayPart);
        }
    }

    await _context.SaveChangesAsync();
}

注:此方案需注意关联实体的状态管理,避免重复更新或遗漏新增/删除操作。

内容的提问来源于stack exchange,提问作者Semyon Falshin

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.09 21:25:20