使用AutoMapper映射EF实体时遇「无法插入重复键」错误求解
问题描述
我通过以下流程更新数据库行:使用Entity Framework 6读取实体,通过AutoMapper将新属性映射到该实体,再调用context.SaveChanges()。但遇到导航属性相关问题:映射过程中为导航属性创建了新对象,而非仅更新现有对象的属性。
领域对象代码
public class ParagraphComponent : IReportComponent { public Guid ComponentId { get; set; } public float LineHeight { get; set; } public ReportTextList TextItems { get; set; } = new ReportTextList(); } public class ReportTextList : IList<ReportText> { private readonly IList<ReportText> _list = new List<ReportText>(); public int Count => _list.Count; public bool IsReadOnly => _list.IsReadOnly; // IList接口的其他实现方法 public void Add(ReportText item) { _list.Add(item); } // ...包含Remove()、Insert()等方法 } public class ReportText { public Guid Id { get; set; } public string Content { get; set; } = ""; }
EF实体代码
public partial class ParagraphComponentEntity { public System.Guid ComponentId { get; set; } // 主键 public double LineHeight { get; set; } public virtual System.Collections.Generic.ICollection<ReportTextEntity> ReportTexts { get; set; } } public partial class ReportTextEntity { public System.Guid Id { get; set; } // 主键 public string Content { get; set; } }
更新操作代码
public async Task<bool> EditComponent(IReportComponent editedComponent) { var currentParagraphComponentEntity = await Context .ParagraphComponents .FirstOrDefaultAsync(x => x.ComponentId == editedComponent.ComponentId) .ConfigureAwait(false); Mapper.Map(editedComponent as ParagraphComponent, currentParagraphComponentEntity); Context.SaveChanges(); }
报错信息
Violation of PRIMARY KEY constraint 'PK_ReportText'. Cannot insert duplicate key in object 'dbo.ReportText'
当前AutoMapper配置
CreateMap<ParagraphComponent, ParagraphComponentEntity>() .ForMember(dest => dest.LineHeight, src => src.MapFrom(s => s.LineHeight)) .ForMember(dest => dest.ReportTexts, src => src.MapFrom(s => s.TextItems)) .ForMember(dest => dest.ComponentId, src => src.MapFrom(s => s.ComponentId)) .ForAllOtherMembers(src => src.Ignore());
问题核心:AutoMapper为ReportTexts导航属性创建新实例,导致EF将其视为新增操作,触发主键重复错误,该如何解决?
解决方案
1. 修改AutoMapper集合映射规则,保留原集合实例
修改配置,让集合属性映射时保留EF跟踪的原集合,而非创建新集合:
CreateMap<ParagraphComponent, ParagraphComponentEntity>() .ForMember(dest => dest.LineHeight, src => src.MapFrom(s => s.LineHeight)) .ForMember(dest => dest.ComponentId, src => src.MapFrom(s => s.ComponentId)) .ForMember(dest => dest.ReportTexts, opt => { opt.MapFrom(s => s.TextItems); // 强制使用目标端已存在的集合实例 opt.UseDestinationValue(); }) .ForAllOtherMembers(src => src.Ignore());
2. 添加单个元素的映射规则
AutoMapper需要明确如何将ReportText映射到ReportTextEntity,添加对应配置:
CreateMap<ReportText, ReportTextEntity>() // 明确主键映射,让AutoMapper识别已存在的实体 .ForMember(dest => dest.Id, src => src.MapFrom(s => s.Id)) .ForMember(dest => dest.Content, src => src.MapFrom(s => s.Content));
3. 显式加载导航属性(关键步骤)
查询EF实体时,必须加载ReportTexts导航属性,否则EF无法跟踪现有集合中的实体,AutoMapper会直接替换集合:
var currentParagraphComponentEntity = await Context .ParagraphComponents // 显式加载ReportTexts导航属性 .Include(p => p.ReportTexts) .FirstOrDefaultAsync(x => x.ComponentId == editedComponent.ComponentId) .ConfigureAwait(false);
4. 支持集合的增删改(可选)
如果需要处理集合元素的新增、删除和修改,可以开启RemoveMissingItems配置,让AutoMapper自动同步集合内容:
// 先配置单个元素的双向映射 CreateMap<ReportText, ReportTextEntity>() .ForMember(dest => dest.Id, src => src.MapFrom(s => s.Id)) .ForMember(dest => dest.Content, src => src.MapFrom(s => s.Content)) .ReverseMap() .ForAllMembers(opt => opt.Ignore()); // 修改集合映射配置,开启移除缺失项 CreateMap<ParagraphComponent, ParagraphComponentEntity>() .ForMember(dest => dest.ReportTexts, opt => { opt.MapFrom(s => s.TextItems); opt.UseDestinationValue(); // 移除源集合中不存在的目标元素 opt.RemoveMissingItems(); }) .ForMember(dest => dest.LineHeight, src => src.MapFrom(s => s.LineHeight)) .ForMember(dest => dest.ComponentId, src => src.MapFrom(s => s.ComponentId)) .ForAllOtherMembers(src => src.Ignore());
内容的提问来源于stack exchange,提问作者MSOACC
相关产品推荐
相关产品推荐

