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

使用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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.14 10:35:21