如何使用AutoMapper对相似项分组并实现双向映射?
这个需求核心是按AppName和Role对Source集合分组,把同组的UserName和Action分别合并成列表,同时还要支持双向映射。我来一步步拆解实现方案:
一、单向映射(Source集合 → Destination集合)
首先得先对Source做分组,因为AutoMapper是对象到对象的映射工具,我们需要把每个分组转换成对应的Destination对象。
1. 配置AutoMapper映射规则
创建一个Profile类,专门定义这两个类型的映射逻辑:
public class SourceDestinationProfile : Profile { public SourceDestinationProfile() { // 配置从分组对象到Destination的映射 CreateMap<IGrouping<(string AppName, string Role), Source>, Destination>() // 从分组里提取所有UserName,转成列表赋值给UserNames .ForMember(dest => dest.UserNames, opt => opt.MapFrom(src => src.Select(s => s.UserName).ToList())) // 同理提取所有Action转成列表 .ForMember(dest => dest.Actions, opt => opt.MapFrom(src => src.Select(s => s.Action).ToList())) // 分组的Key里已经包含了唯一的AppName和Role,直接取就行(同组元素这两个字段肯定一致) .ForMember(dest => dest.AppName, opt => opt.MapFrom(src => src.Key.AppName)) .ForMember(dest => dest.Role, opt => opt.MapFrom(src => src.Key.Role)); } }
2. 执行映射流程
先初始化AutoMapper,然后对Source集合分组,最后完成映射:
// 项目启动时只需要配置一次AutoMapper var config = new MapperConfiguration(cfg => cfg.AddProfile<SourceDestinationProfile>()); var mapper = config.CreateMapper(); // 你的示例Source集合 var sources = new List<Source> { new Source { UserName = "John", AppName = "MyApplication", Role = "Admin", Action = "Add" }, new Source { UserName = "Elis", AppName = "MyApplication", Role = "Admin", Action = "Edit" } }; // 按AppName和Role分组,确保同组的元素这两个字段完全一致 var groupedSources = sources.GroupBy(s => (s.AppName, s.Role)); // 把分组后的集合映射成Destination列表 var destinations = mapper.Map<List<Destination>>(groupedSources);
运行这段代码后,就能得到你期望的结果:单个Destination对象,UserNames包含["John", "Elis"],Actions包含["Add", "Edit"],AppName和Role保持为"MyApplication"和"Admin"。
二、双向映射(Destination → Source集合)
反向映射需要把一个Destination对象还原成多个Source对象,但这里有个需要注意的点:当前的Destination结构丢失了用户与动作的关联关系(比如不知道John对应Add还是Edit)。如果你的业务场景是同一组的所有用户都拥有该组的所有动作,那我们可以生成所有用户-动作的组合;如果需要精准还原原始的Source集合,那可能需要调整Destination的结构(比如新增子对象保存用户和动作的对应关系)。
1. 添加反向映射规则
在之前的Profile里补充反向映射的自定义转换逻辑:
public class SourceDestinationProfile : Profile { public SourceDestinationProfile() { // 正向映射(分组→Destination) CreateMap<IGrouping<(string AppName, string Role), Source>, Destination>() .ForMember(dest => dest.UserNames, opt => opt.MapFrom(src => src.Select(s => s.UserName).ToList())) .ForMember(dest => dest.Actions, opt => opt.MapFrom(src => src.Select(s => s.Action).ToList())) .ForMember(dest => dest.AppName, opt => opt.MapFrom(src => src.Key.AppName)) .ForMember(dest => dest.Role, opt => opt.MapFrom(src => src.Key.Role)); // 反向映射(Destination→Source集合) CreateMap<Destination, IEnumerable<Source>>() .ConvertUsing(dest => // 将每个UserName和每个Action组合,生成对应的Source对象 dest.UserNames.SelectMany(userName => dest.Actions.Select(action => new Source { UserName = userName, AppName = dest.AppName, Role = dest.Role, Action = action }))); } }
2. 执行反向映射
直接把Destination对象传给AutoMapper,就能得到对应的Source集合:
// 示例Destination对象 var destination = new Destination { UserNames = new List<string> { "John", "Elis" }, AppName = "MyApplication", Role = "Admin", Actions = new List<string> { "Add", "Edit" } }; // 反向映射得到Source列表 var sources = mapper.Map<List<Source>>(destination);
这段代码会生成4个Source对象:John-Add、John-Edit、Elis-Add、Elis-Edit。如果你的业务场景需要精准还原原始的2条记录,那建议调整Destination结构,比如用List<UserActionPair>代替分开的UserNames和Actions,这样反向映射时就能准确对应关系。
内容的提问来源于stack exchange,提问作者Денис Дягилев

