You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何配置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的映射,满足:

  1. 目标类的匹配字段优先从SourceClass基类FieldValues的Field/Value中映射;
  2. 同时正常映射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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.18 06:23:14