AutoMapper映射只读目标子对象异常及规则疑问
这个问题其实涉及AutoMapper对只读属性和集合类型的不同默认处理逻辑,咱们一步步拆解:
1. 为什么你的Child属性需要显式MapFrom?
你的Source.Child和Destination.Child都是只读的引用类型属性(只有get访问器,实例在构造函数中初始化)。AutoMapper的默认规则是:
对于无
setter的引用类型属性,AutoMapper默认不会主动映射其内部成员——因为它默认认为,无setter意味着你不希望外部替换这个属性的实例引用,所以会跳过对该属性内部的映射操作。
在你最初的配置中,只创建了Source→Destination和SourceChild→DestinationChild的映射,但AutoMapper看到Destination.Child没有setter,就不会去触发SourceChild→DestinationChild的映射逻辑来同步内部的B值。
当你添加.ForMember(e => e.Child, o => o.MapFrom(e => e.Child))后,相当于明确告诉AutoMapper:我需要把源对象的Child的属性映射到目标对象已存在的Child实例上。这时候AutoMapper才会调用已配置的SourceChild→DestinationChild映射,把B=2同步过去。
2. 为什么嵌套的只读List<T>可以自动映射?
AutoMapper对集合类型(比如List<T>)有特殊的处理逻辑:
如果目标集合实例已经存在(比如在构造函数中初始化了
new List<T>()),即使集合属性是只读的,AutoMapper也会复用这个已存在的集合实例,自动将源集合的元素映射并添加/替换到目标集合中。
而嵌套对象中的只读List<T>能自动映射的原因是:当AutoMapper处理父对象的映射(比如SourceChild→DestinationChild)时,会遍历父对象的所有可映射成员——包括集合属性。这时候集合映射的特殊逻辑就会生效,无需额外配置。
3. 根对象只读List<T>与嵌套只读List<T>的行为差异
你观察到的行为差异,核心原因在于根对象属性和嵌套对象属性的映射触发逻辑不同:
- 根对象的只读
List<T>属性:AutoMapper默认会跳过无setter的属性,不会主动触发集合映射逻辑,所以需要显式MapFrom来告诉它要处理这个属性; - 嵌套对象的只读
List<T>属性:因为AutoMapper已经在处理父对象的完整映射,会遍历父对象的所有成员,集合映射的特殊逻辑会自动触发,所以无需额外配置。
补充:验证你的解决方法
你添加显式MapFrom的解决方法是完全正确的。另外,如果你不想写MapFrom,也可以用另一种方式强制AutoMapper映射只读属性的内部成员:
this.CreateMap<Source, Destination>() .ForMember(dest => dest.Child, opt => opt.UseDestinationValue()) .ForMember(dest => dest.Child.B, opt => opt.MapFrom(src => src.Child.B));
不过显然,直接用MapFrom映射整个Child属性更简洁。
内容的提问来源于stack exchange,提问作者Zoltán Tamási

