AutoMapper映射含SortedSet构造参数的Parent对象失败求助
AutoMapper映射SortedSet到Parent构造函数失败问题分析与解决
问题背景
使用AutoMapper v12.0.1定义如下类与映射配置:
public record Parent(SortedSet<Child> Children); public record Child(int Index); public class ChildComparer : IComparer<Child> { public int Compare(Child x, Child y) { return x.Index.CompareTo(y.Index); } } var mapper = new MapperConfiguration( cfg => { cfg.CreateMap<int, Child>() .ForCtorParam("Index", opt => opt.MapFrom(src => src)); cfg.CreateMap<IEnumerable<int>, Parent>() .ForCtorParam("Children", opt => opt.MapFrom((src, ctx) => new SortedSet<Child>( ctx.Mapper.Map<List<Child>>(src.ToList()), new ChildComparer()))); }) .CreateMapper();
以下操作均可正常执行:
var ints = new List<int> { 1, 2, 3 }; var res = mapper.Map<Child>(2); var res2 = mapper.Map<List<Child>>(ints); var resSet = new SortedSet<Child>( mapper.Map<List<Child>>(ints), new ChildComparer());
但执行映射生成Parent对象时失败:
var result = mapper.Map<Parent>(ints); // System.ArgumentException: At least one object must implement IComparable.
完整错误信息:
Unhandled exception. AutoMapper.AutoMapperMappingException: Error mapping types. Mapping types: IEnumerable`1 -> Parent System.Collections.Generic.IEnumerable`1[[System.Int32, System.Private.CoreLib, Version=7.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]] -> Program+Parent Type Map configuration: IEnumerable`1 -> Parent System.Collections.Generic.IEnumerable`1[[System.Int32, System.Private.CoreLib, Version=7.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]] -> Program+Parent Destination Member: Void .ctor(System.Collections.Generic.SortedSet`1[Program+Child]), parameter Children ---> System.ArgumentException: At least one object must implement IComparable. at System.Collections.Comparer.Compare(Object a, Object b) at System.Collections.Generic.SortedSet`1.AddIfNotPresent(T item) at System.Collections.Generic.SortedSet`1.System.Collections.Generic.ICollection<T>.Add(T item) at lambda_method3(Closure, Object, Parent, ResolutionContext) --- End of inner exception stack trace --- at lambda_method3(Closure, Object, Parent, ResolutionContext) at Program.Main() Command terminated by signal 6
需求:找出问题原因,且尽可能继续使用.ForCtorParam方法和自定义Comparer类。
问题原因
AutoMapper对集合类型的构造函数参数存在默认行为:即使你通过ForCtorParam直接构造好了SortedSet<Child>实例,它仍会将该实例视为空目标集合,尝试把源IEnumerable<int>的元素逐个映射后添加进去。此时添加元素时,AutoMapper并未使用你传入的自定义ChildComparer,而是默认启用Comparer<Child>.Default——但Child类型未实现IComparable<Child>接口,因此触发了At least one object must implement IComparable错误。
解决方案
要保留.ForCtorParam和自定义Comparer,只需在配置中告诉AutoMapper不要对该构造函数参数执行自动集合填充操作,添加opt.DoNotValidate()即可:
var mapper = new MapperConfiguration( cfg => { cfg.CreateMap<int, Child>() .ForCtorParam("Index", opt => opt.MapFrom(src => src)); cfg.CreateMap<IEnumerable<int>, Parent>() .ForCtorParam("Children", opt => { opt.MapFrom((src, ctx) => new SortedSet<Child>( ctx.Mapper.Map<List<Child>>(src.ToList()), new ChildComparer())); // 禁用AutoMapper对该参数的集合验证与自动填充 opt.DoNotValidate(); }); }) .CreateMapper();
备选方案(不推荐,仅作参考)
如果允许修改Child类型,也可以让其实现IComparable<Child>接口,这样即使AutoMapper使用默认比较器也能正常工作:
public record Child(int Index) : IComparable<Child> { public int CompareTo(Child? other) { return other is null ? 1 : Index.CompareTo(other.Index); } }
但此方案会让Child自带排序逻辑,与自定义ChildComparer的设计初衷冲突,因此优先推荐第一种解决方案。
内容的提问来源于stack exchange,提问作者R4pt0r
相关产品推荐
相关产品推荐

