Automapper ProjectTo:子级访问父级及父对象传递至子集合的解决方案
解决AutoMapper ProjectTo的两个关键需求
我之前处理过完全相同的场景,给你分享两个实用的解决方案,完美适配你提到的需求:
1. 让子级DTO访问父级对象
在ProjectTo的LINQ投影场景下,要让子级获取父级数据,核心是利用AutoMapper的映射上下文或者实体间的导航属性。举个具体的例子:
假设我们有这些实体类:
public class Parent { public int Id { get; set; } public string ProfileName { get; set; } public ICollection<Child> Children { get; set; } } public class Child { public int Id { get; set; } public string Name { get; set; } // 如果你的Child实体有父级导航属性,那会更方便 public Parent Parent { get; set; } }
对应的DTO:
public class ParentDto { public int Id { get; set; } public string ProfileName { get; set; } public ICollection<ChildDto> Children { get; set; } } public class ChildDto { public int Id { get; set; } public string Name { get; set; } public string ParentProfileName { get; set; } // 需要从父级获取的属性 }
方案A:利用实体导航属性(推荐)
如果Child实体有Parent导航属性,直接在映射配置中引用父级属性即可:
// 父级映射 CreateMap<Parent, ParentDto>(); // 子级映射,直接通过导航属性获取父级ProfileName CreateMap<Child, ChildDto>() .ForMember(dest => dest.ParentProfileName, opt => opt.MapFrom(src => src.Parent.ProfileName));
调用ProjectTo时记得Include父级导航(如果需要):
var parentDtos = dbContext.Parents .Include(p => p.Children) // 如果Child的Parent导航是延迟加载,这里需要Include .ProjectTo<ParentDto>(_mapper.ConfigurationProvider) .ToList();
方案B:无导航属性时的投影传递
如果Child没有Parent导航属性,就在父级映射中显式投影子集合,直接传递父级数据:
CreateMap<Parent, ParentDto>() .ForMember(dest => dest.Children, opt => opt.MapFrom(src => src.Children.Select(child => new ChildDto { Id = child.Id, Name = child.Name, ParentProfileName = src.ProfileName }))); // 子级基础映射(可选,因为上面已经显式投影了) CreateMap<Child, ChildDto>() .ForMember(dest => dest.Id, opt => opt.MapFrom(src => src.Id)) .ForMember(dest => dest.Name, opt => opt.MapFrom(src => src.Name));
这样调用ProjectTo时,AutoMapper会生成正确的LINQ表达式,直接从数据库中投影出需要的数据。
2. 绕过BeforeMap,将父对象传递给子集合填充ProfileName
因为ProjectTo是基于LINQ的服务器端投影,AutoMapper的BeforeMap方法在这里完全不生效(它只在内存映射时起作用)。所以我们需要换一种思路:直接在映射配置中把父级的ProfileName传递给子集合的每个元素。
沿用上面的实体和DTO结构,假设ChildDto需要填充ProfileName属性(来自父级),可以这样配置:
CreateMap<Parent, ParentDto>() .ForMember(dest => dest.Children, opt => opt.MapFrom(src => src.Children.Select(c => new ChildDto { Id = c.Id, Name = c.Name, ProfileName = src.ProfileName // 直接传递父级的ProfileName }))); // 子级映射保持基础属性的映射即可 CreateMap<Child, ChildDto>() .ForMember(dest => dest.Id, opt => opt.MapFrom(src => src.Id)) .ForMember(dest => dest.Name, opt => opt.MapFrom(src => src.Name));
这种方式的好处是:
- 完全兼容LINQ to Entities,所有投影都在数据库端完成,性能最优
- 不需要依赖任何上下文传递,代码清晰直观
如果你更倾向于使用AutoMapper的映射规则而不是显式投影,也可以通过映射上下文传递:
CreateMap<Parent, ParentDto>() .ForMember(dest => dest.Children, opt => opt.MapFrom((src, dest, _, context) => src.Children.Select(child => context.Mapper.Map<ChildDto>(child, opt => opt.Items["ParentProfileName"] = src.ProfileName)))); CreateMap<Child, ChildDto>() .ForMember(dest => dest.ProfileName, opt => opt.MapFrom((src, dest, _, context) => context.Items["ParentProfileName"]));
不过要注意,这种方式在某些ORM中可能会触发客户端求值,所以优先推荐第一种显式投影的方案。
内容的提问来源于stack exchange,提问作者Alexandre
相关产品推荐
相关产品推荐

