AutoMapAttribute with ReverseMap & TypeConverter 创建 2 路地图?



我有一个与之通信的设备。它以各种整数表示(byte,ushort,uint)返回一些位标志。

目前,为了在UI上显示它们,它们被映射到ViewModels:

// The ViewModel, annotated
[AutoMap(typeof(byte), TypeConverter = typeof(FlagConverter))]
public record FlagVM(bool One, bool Two)
{ 
// And its converter
public class FlagConverter : ITypeConverter<byte, FlagVM> {
public FlagVM Convert(byte src, FlagVM dst, ResolutionContext c)
=> new((src & 1) > 0, (src & 2) > 0);
}

使用AutoMapAttribute,因为有50多个其他结构,AutoMapper可以很容易地配置为整个汇编:

var mapper = new MapperConfiguration(cfg =>
cfg.AddMaps(this.GetType().Assembly)
).CreateMapper();
mapper.Map<FlagVM>((byte)2)
.Should().Be(new FlagVM(false, true)); //easy!

现在,问题来了:我还需要创建反向映射,返回到数字表示。很容易添加到转换器中:

public class FlagConverter
: ITypeConverter<byte, FlagVM>, ITypeConverter<FlagVM, byte> {
public FlagVM Convert(byte src, FlagVM dst, ResolutionContext c)
=> new(One:(src & 1) > 0, Two:(src & 2) > 0);
public byte Convert(FlagVM src, byte dst, ResolutionContext c)
=> (byte)((src.One ? 1 : 0) | (src.Two ? 2 : 0));
}

这一切都很好,除了现在我不能再使用AutoMapAttribute了,因为简单地添加ReverseMap不起作用:

// The TypeConverter is not applied to the reverse map
[AutoMap(typeof(byte), TypeConverter = typeof(FlagConverter), ReverseMap = true)]
我能得到双向映射的唯一方法是配置每一个(手动或反射)
var mapper = new MapperConfiguration(cfg =>
cfg.CreateMap<byte, FlagDto>().ConvertUsing<FlagConverter>();
cfg.CreateMap<FlagDto, byte>().ConvertUsing<FlagConverter>(); //reverse
// .. repeat 50+ times
// .. or use reflection to find all ITypeConverter<> implementations.
).CreateMapper();
// Forward map
mapper.Map<FlagVM>((byte)2).Should().Be(new FlagVM(false, true));
// Reverse map
mapper.Map<byte>(new FlagVM(false, true)).Should().Be(2);

是的,在一天结束的时候,AutoMapper会做反射来找到属性;但是整个程序是使用基于属性的映射来配置的,我更喜欢这些结构与其他代码库保持一致。

真的没有办法结合AutoMapAttribute,ReverseMapTypeConverter来创建双向地图吗?

注:.NET6, AutoMapper 11.0

正如在评论中所指出的,这不是可以用属性API做的事情。

我们最终通过简单地扫描整个程序集中的所有ITypeConverter<,>实现并逐一注册它们来自动化流畅注册。

private static void CreateTypeConverterMaps(IMapperConfigurationExpression mapperCfg) {
var converters = typeof(AType).Assembly
.GetTypes()
.SelectMany(typ => typ
.GetInterfaces()
.Where(itf => itf.IsGenericType)
.Where(itf => itf.GetGenericTypeDefinition() == typeof(ITypeConverter<,>))
.Select(itf => new { Interface = itf, Class = typ }));
foreach (var conv in converters) {
// We know there's two TypeArgs, as we filter by <,> above
var srcType = conv.Interface.GenericTypeArguments[0];
var dstType = conv.Interface.GenericTypeArguments[1];
mapperCfg // Register the map+converter
.CreateMap(srcType, dstType)
.ConvertUsing(conv.Class);
}
}

最新更新