如何配置AutoMapper实现基类字典值与类成员的映射?
问题背景
我有以下C#类:
public class FieldValue { public string Field { get; set; } public object Value { get; set; } } public class FieldValues { public FieldValue[] Values { get; set; } } public static class FieldValuesExtensions { public static IDictionary<string, object> ToDictionary(this FieldValues self) { return self.Values.ToDictionary(v => v.Field, v => v.Value); } } public class SourceClass : FieldValues { public int Id { get; set; } public string Name { get; set; } } public class DestinationClassA { public int Id { get; set; } public string Field1 { get; set; } public string Field2 { get; set; } } public class DestinationClassB { public string Name { get; set; } public string Field2 { get; set; } public string Field3 { get; set; } }
SourceClass继承自FieldValues并拥有自有字段,DestinationClassX类型仅包含普通字段。
映射需求
需要实现从SourceClass到DestinationClassX的映射,满足:
- 目标类的匹配字段优先从
SourceClass基类FieldValues的Field/Value中映射; - 同时正常映射
SourceClass自身的匹配成员。
测试示例如下:
public class TestClass { public static void Test(IMapper mapper) { // 创建SourceClass对象 SourceClass source = new() { Id = 123, Name = "Foo", Values = new FieldValue[] { new() { Field = "Field1", Value = "Value1" }, new() { Field = "Field2", Value = "Value2" }, new() { Field = "Field3", Value = "Value3" }, new() { Field = "Field4", Value = "Value4" } } }; // 映射到DestinationClassA DestinationClassA destinationA = mapper.Map<SourceClass, DestinationClassA>(source); // 预期结果: // destinationA.Id == 123; // destinationA.Field1 == "Value1"; // destinationA.Field2 == "Value2"; // 映射到DestinationClassB DestinationClassB destinationB = mapper.Map<SourceClass, DestinationClassB>(source); // 预期结果: // destinationB.Name == "Foo"; // destinationB.Field2 == "Value2"; // destinationB.Field3 == "Value3"; } }
现有问题
已知AutoMapper可以自动从动态对象或IDictionary<string, object>映射。之前尝试了两种映射配置:
// 映射1:仅映射SourceClass自身的字段 CreateMap<SourceClass, DestinationClassA>(); // 映射2:仅从字典映射字段 CreateMap<SourceClass, DestinationClassWithA>() .ForMember(dst => dst.A, opt => opt.MapFrom(src => src.ToDictionary()));
其中DestinationClassWithA的定义为:
public class DestinationClassWithA { public DestinationClassA A { get; set; } }
映射2中Destination.A的Field1和Field2能被正确填充,但Id字段为null;注释映射2后,映射1能正常填充Id但无法获取字典中的条目。现在需要把两种映射逻辑整合,实现如下配置:
CreateMap<SourceClass, DestinationClassA>() // 此处需要添加什么配置? CreateMap<SourceClass, DestinationClassB>() // 此处需要添加什么配置?
解决方案
方法1:自定义值解析器
创建通用值解析器,优先从字典取值,找不到则回退到SourceClass自身属性:
public class SourceClassValueResolver<TSource, TDestination, TMember> : IValueResolver<TSource, TDestination, TMember> where TSource : SourceClass { public TMember Resolve(TSource source, TDestination destination, TMember member, ResolutionContext context) { var dict = source.ToDictionary(); // 优先从字典取值 if (dict.TryGetValue(context.MemberName, out var value)) { return (TMember)Convert.ChangeType(value, typeof(TMember)); } // 字典无匹配则读取自身属性 var property = typeof(TSource).GetProperty(context.MemberName); if (property != null) { return (TMember)property.GetValue(source); } return default; } }
配置映射时对所有成员应用该解析器:
CreateMap<SourceClass, DestinationClassA>() .ForAllMembers(opt => opt.MapFrom<SourceClassValueResolver<SourceClass, DestinationClassA, object>>()); CreateMap<SourceClass, DestinationClassB>() .ForAllMembers(opt => opt.MapFrom<SourceClassValueResolver<SourceClass, DestinationClassB, object>>());
方法2:AfterMap手动合并字典值
先让AutoMapper自动映射SourceClass自身字段,再通过AfterMap覆盖字典中的匹配值:
CreateMap<SourceClass, DestinationClassA>() .AfterMap((src, dst) => { var dict = src.ToDictionary(); foreach (var prop in typeof(DestinationClassA).GetProperties()) { if (dict.TryGetValue(prop.Name, out var value) && value != null) { prop.SetValue(dst, Convert.ChangeType(value, prop.PropertyType)); } } }); CreateMap<SourceClass, DestinationClassB>() .AfterMap((src, dst) => { var dict = src.ToDictionary(); foreach (var prop in typeof(DestinationClassB).GetProperties()) { if (dict.TryGetValue(prop.Name, out var value) && value != null) { prop.SetValue(dst, Convert.ChangeType(value, prop.PropertyType)); } } });
方法3:全局类型转换器
创建通用类型转换器,先映射自身属性再合并字典值:
public class SourceClassConverter<TDestination> : ITypeConverter<SourceClass, TDestination> where TDestination : new() { public TDestination Convert(SourceClass source, TDestination destination, ResolutionContext context) { // 先映射SourceClass自身属性 destination = context.Mapper.Map<SourceClass, TDestination>(source, opt => opt.ExcludeAllMembers()); var dict = source.ToDictionary(); // 合并字典中的匹配值 foreach (var prop in typeof(TDestination).GetProperties()) { if (dict.TryGetValue(prop.Name, out var value) && value != null) { prop.SetValue(destination, Convert.ChangeType(value, prop.PropertyType)); } } return destination; } }
注册转换器到对应映射:
CreateMap<SourceClass, DestinationClassA>().ConvertUsing<SourceClassConverter<DestinationClassA>>(); CreateMap<SourceClass, DestinationClassB>().ConvertUsing<SourceClassConverter<DestinationClassB>>();
注意:若字典值类型与目标属性类型不兼容,需自行添加类型转换异常处理逻辑。
内容的提问来源于stack exchange,提问作者Petrock
相关产品推荐
相关产品推荐

