如何使用AutoMapper将PersonEntity列表结合NameMode枚举映射为PersonDto列表?
我明白你遇到的问题了——你已经搞定了单个(PersonEntity, NameMode)元组到PersonDto的映射,但批量处理列表的时候踩了两个坑:直接传(List<PersonEntity>, NameMode)给Map方法,AutoMapper找不到对应的映射配置;用ProjectTo的时候又碰到了表达式树不支持元组字面量的语法错误。
下面给你几个可行的解决方案,你可以根据自己的场景来选:
方案一:用自定义包装类替代元组(适合EF查询投影场景)
C#的表达式树确实不支持元组字面量,但它能解析自定义类的实例创建。我们先搞一个包装类,把PersonEntity和NameMode包起来:
public class PersonWithNameMode { public PersonEntity Person { get; set; } public NameMode Mode { get; set; } }
然后修改你的AutoMapper映射配置,把源类型换成这个包装类:
CreateMap<PersonWithNameMode, PersonDto>() .ForMember(dest => dest.Name, opt => opt.MapFrom<NameCustomResolver>());
记得调整你的NameCustomResolver解析器,现在源是PersonWithNameMode,所以要从source.Person里拿姓名,从source.Mode里拿枚举值。
之后在查询投影的时候就可以正常写了:
var result = source.AsQueryable() .Select(p => new PersonWithNameMode { Person = p, Mode = NameMode.full }) .ProjectTo<PersonDto>(mapper.ConfigurationProvider);
这样EF就能正确解析表达式树,完成投影映射。
方案二:内存中逐个映射(适合数据已在内存的场景)
如果你的source列表已经是内存中的数据(不是EF的IQueryable),那最简单的方式就是遍历每个元素,和枚举组合成元组后逐个映射:
List<PersonDto> destination = source .Select(p => mapper.Map<PersonDto>((p, NameMode.full))) .ToList();
这种方式完全不用修改你原来的元组映射配置,直接复用已有的规则,简单粗暴又有效。
方案三:利用AutoMapper上下文传递枚举(最灵活的方案)
其实我们可以不用元组或者包装类,直接把NameMode放到AutoMapper的映射上下文里,让解析器从上下文里取枚举值。
先修改映射配置,把源换回PersonEntity:
// 如果你用内联映射逻辑 CreateMap<PersonEntity, PersonDto>() .ForMember(dest => dest.Name, opt => opt.MapFrom((src, _, __, context) => { var mode = (NameMode)context.Items["NameMode"]; return mode == NameMode.full ? $"{src.FirstName} {src.LastName}" : src.FirstName; })); // 如果你还是想用自定义解析器 public class NameCustomResolver : IValueResolver<PersonEntity, PersonDto, string> { public string Resolve(PersonEntity source, PersonDto destination, string destMember, ResolutionContext context) { var mode = (NameMode)context.Items["NameMode"]; return mode == NameMode.full ? $"{source.FirstName} {source.LastName}" : source.FirstName; } } // 对应的映射配置 CreateMap<PersonEntity, PersonDto>() .ForMember(dest => dest.Name, opt => opt.MapFrom<NameCustomResolver>());
然后映射列表的时候,通过Map的重载传递上下文参数:
List<PersonDto> destination = mapper.Map<List<PersonDto>>(source, opt => { opt.Items["NameMode"] = NameMode.full; });
这种方式不需要额外定义类,配置更简洁,而且你可以在不同的映射场景中传递不同的NameMode值,灵活性拉满。
备注:内容来源于stack exchange,提问作者maces13

