EF Core使用AutoMapper时Seo实体新增而非更新的解决方法
问题根因
_mapper.Map<Seo>(text)会生成一个全新的Seo类实例,你把这个新实例赋值给text.Seo导航属性时,EF Core的变更追踪检测到:
- 原有的Seo实例是被追踪的,但现在导航属性指向了一个未被追踪的新对象
- 映射配置中忽略了Id属性,新生成的Seo实例主键为默认值0
因此EF Core会判定这是一个待新增的实体,每次执行SaveChanges都会插入新的Seo记录。
而手动给text.Seo的各个属性赋值时,操作的是EF Core已经加载并追踪的原有Seo实例,变更追踪会识别到属性值修改,最终执行更新操作,符合预期。
解决方案
优先选择方案1,改动最小、逻辑最稳妥。
方案1:直接映射到已追踪的现有Seo实例
使用AutoMapper的源、目标对象重载,不创建新实例,直接把Texts的属性值覆盖到EF已经追踪的text.Seo实例上,和手动赋值的执行逻辑完全一致。
只需要修改业务层代码即可,不需要调整现有AutoMapper配置:
Texts text = await _context.Texts.Include(x => x.Seo).Where(x => x.Id == id).FirstOrDefaultAsync(); // 第二个参数传入已存在的被追踪实例,直接在原实例上更新属性 _mapper.Map(text, text.Seo); await _unitOfWork.SaveChangesAsync();
该方案保留了你之前配置的Id忽略规则,不会修改原有Seo的主键值,也不会触发实体跟踪冲突。
方案2:映射新实例时保留原有主键值
如果你确实需要生成新的Seo实例替换导航属性,需要在映射时把原有Seo的主键值传入新实例,EF Core检测到主键和已追踪实体匹配时,就不会判定为新增实体。
首先调整AutoMapper配置,通过映射上下文传入原有Seo的Id:
CreateMap<Texts, Seo>() // 移除原有的Id忽略配置,改为从映射上下文中读取原有Id赋值 .ForMember(dest => dest.Id, opt => opt.MapFrom((src, dest, _, ctx) => ctx.Items["originSeoId"])) .ForMember(dest => dest.Image, opt => opt.MapFrom(src => src.Image.InverseParent.OrderByDescending(x => x.Width).FirstOrDefault())) .ForMember(dest => dest.KeyWords, opt => opt.MapFrom(src => src.Title.ToKeywords())) .ForMember(dest => dest.LanguageType, opt => opt.MapFrom(src => src.LanguageType)) .ForMember(dest => dest.OgTitle, opt => opt.MapFrom(src => src.Title)) .ForMember(dest => dest.OgDescription, opt => opt.MapFrom(src => src.Description));
修改业务调用代码,传入原有Seo的主键:
Texts text = await _context.Texts.Include(x => x.Seo).Where(x => x.Id == id).FirstOrDefaultAsync(); text.Seo = _mapper.Map<Seo>(text, opts => opts.Items["originSeoId"] = text.Seo.Id); await _unitOfWork.SaveChangesAsync();
注意:方案2需要额外处理实体跟踪状态,若上下文已经追踪了同Id的Seo实体,可能会抛出跟踪冲突错误,非必要不选该方案。
内容的提问来源于stack exchange,提问作者Offir
相关产品推荐
相关产品推荐

