Automapper 12映射EF实体时关联实体忽略规则失效问题求助
问题
使用Automapper 12将模型映射到现有EF实体时,主实体的忽略规则正常生效(UserId未被修改),但关联实体的忽略规则失效:关联的Ticket对象被创建为新对象,TicketId变成0,不符合“TicketId不被修改”的预期。
代码示例
业务逻辑代码
var model = new User() { UserId = 1, Name = "Joe", Tickets = new List<Ticket>() { new Ticket { TicketId = 1, Title = "Test Ticket", } } }; var entity = await context.User.Include(x => x.Tickets).Where(x => x.UserId == userId).FirstOrDefaultAsync(); entity = _mapper.Map(model, entity); await context.SaveChangesAsync();
Automapper配置
// mapping profile public class UserProfile : Profile { public UserProfile() { CreateMap<DataModel.User, ApiModel.User>() .ForMember(x => x.Name, options => options.MapFrom(x => x.Name)) .ForMember(x => x.Tickets, options => options.MapFrom(x => x.Tickets)) .ForMember(x => x.UserId, options => options.Ignore()); CreateMap<DataModel.Ticket, ApiModel.Ticket>() .ForMember(x => x.Title, options => options.MapFrom(x => x.Title)) .ForMember(x => x.TicketId, options => options.Ignore()); } }
原因分析
核心问题是Automapper默认会替换整个集合,而非更新集合内的现有实体。映射User的Tickets时,Automapper直接用model里的新List
解决方法
方法1:配置集合映射保留原有集合
在User的映射规则中,给Tickets集合添加UseDestinationValue配置,告诉Automapper不要替换整个集合,而是基于原有集合进行更新:
CreateMap<DataModel.User, ApiModel.User>() .ForMember(x => x.Name, options => options.MapFrom(x => x.Name)) .ForMember(x => x.Tickets, options => options.UseDestinationValue()) // 关键:保留目标集合 .ForMember(x => x.UserId, options => options.Ignore());
方法2:配置实体匹配规则,映射现有集合元素
需要让Automapper通过主键(TicketId)匹配model和entity中的Ticket,确保更新的是现有实体而非创建新对象。可以在集合映射时自定义逻辑:
CreateMap<DataModel.Ticket, ApiModel.Ticket>() .ForMember(x => x.Title, options => options.MapFrom(x => x.Title)) .ForMember(x => x.TicketId, options => options.Ignore()); CreateMap<DataModel.User, ApiModel.User>() .ForMember(dest => dest.Name, opt => opt.MapFrom(src => src.Name)) .ForMember(dest => dest.UserId, opt => opt.Ignore()) .ForMember(dest => dest.Tickets, opt => opt.MapFrom((src, dest) => src.Tickets.Select(srcTicket => // 找到目标集合中匹配的Ticket,映射属性;如果不存在可选择新增(按需开启) dest.Tickets.FirstOrDefault(t => t.TicketId == srcTicket.TicketId) // ?? new ApiModel.Ticket { TicketId = srcTicket.TicketId } ).ToList() ));
方法3:手动遍历集合逐个映射
如果Automapper的自动配置达不到预期,直接手动处理集合映射更直观:
var entity = await context.User.Include(x => x.Tickets).Where(x => x.UserId == userId).FirstOrDefaultAsync(); // 先映射主实体属性 _mapper.Map(model, entity); // 逐个匹配并更新Ticket foreach (var srcTicket in model.Tickets) { var destTicket = entity.Tickets.FirstOrDefault(t => t.TicketId == srcTicket.TicketId); if (destTicket != null) { _mapper.Map(srcTicket, destTicket); } // 需要新增Ticket的话,取消下方注释 // else // { // var newTicket = _mapper.Map<ApiModel.Ticket>(srcTicket); // entity.Tickets.Add(newTicket); // } } await context.SaveChangesAsync();
关键提示
- 避免集合被替换是核心,必须让Automapper在现有集合的基础上更新元素,而非生成新集合。
- 对于EF跟踪的实体,一定要确保映射的是已存在的实体实例,否则EF会将新实例当作待插入记录,导致主键为默认值。
内容的提问来源于stack exchange,提问作者Michael Brown
相关产品推荐
相关产品推荐

