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

Automapper ForAllPropertyMaps()谓词存在意外副作用问题排查

AutoMapper 11.0.1 中 ForAllPropertyMaps 注册解析器的异常问题排查

问题场景

在.NET 6控制台应用中使用AutoMapper 11.0.1,通过ForAllPropertyMaps()注册自定义解析器,核心逻辑是检测源端Nullable类型属性:当源属性HasValue为false时,返回目标属性原有值以保留原值。但发现仅注释掉获取pm.SourceType的代码(谓词逻辑始终返回true,未做任何功能修改),映射行为就出现异常:目标属性被设置为default(T)而非保留原值。

完整演示代码

using AutoMapper;
using System;

namespace AutoMapperNullableIssue
{
    class Source
    {
        public int? NullableInt { get; set; }
        public string StringProp { get; set; }
    }

    class Destination
    {
        public int NullableInt { get; set; }
        public string StringProp { get; set; }
    }

    class NullablePreserveResolver<TSource, TDestination, TMember> : IValueResolver<TSource, TDestination, TMember>
    {
        public TMember Resolve(TSource source, TDestination destination, TMember destMember, ResolutionContext context)
        {
            var sourceProp = context.MemberMap.SourceMember;
            if (sourceProp.PropertyType.IsGenericType && sourceProp.PropertyType.GetGenericTypeDefinition() == typeof(Nullable<>))
            {
                var hasValue = (bool)sourceProp.PropertyType.GetProperty("HasValue").GetValue(source);
                if (!hasValue)
                {
                    return destMember;
                }
            }
            return (TMember)sourceProp.GetValue(source);
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            // 工作场景配置:保留pm.SourceType访问
            var workingConfig = new MapperConfiguration(cfg =>
            {
                cfg.CreateMap<Source, Destination>();
                cfg.ForAllPropertyMaps(pm => 
                {
                    var type = pm.SourceType;
                    return true;
                }, (pm, c) => c.ResolveUsing<NullablePreserveResolver<Source, Destination, object>>());
            });

            // 失败场景配置:注释掉pm.SourceType访问
            var failingConfig = new MapperConfiguration(cfg =>
            {
                cfg.CreateMap<Source, Destination>();
                cfg.ForAllPropertyMaps(pm => 
                {
                    // var type = pm.SourceType;
                    return true;
                }, (pm, c) => c.ResolveUsing<NullablePreserveResolver<Source, Destination, object>>());
            });

            // 测试工作场景
            var workingMapper = workingConfig.CreateMapper();
            var destWorking = new Destination { NullableInt = 10, StringProp = "Original" };
            var source = new Source { NullableInt = null, StringProp = "New" };
            workingMapper.Map(source, destWorking);
            Console.WriteLine($"Working: NullableInt={destWorking.NullableInt}, StringProp={destWorking.StringProp}");
            // 输出:Working: NullableInt=10, StringProp=New

            // 测试失败场景
            var failingMapper = failingConfig.CreateMapper();
            var destFailing = new Destination { NullableInt = 10, StringProp = "Original" };
            failingMapper.Map(source, destFailing);
            Console.WriteLine($"Failing: NullableInt={destFailing.NullableInt}, StringProp={destFailing.StringProp}");
            // 输出:Failing: NullableInt=0, StringProp=New
        }
    }
}

工作/失败场景的表达式树对比

两段表达式树代码文本完全一致,但实际生成的映射逻辑存在底层差异:

.ResolveUsing((Source src, Destination dest, int destMember, ResolutionContext ctx) => 
{
    var sourceProp = ctx.MemberMap.SourceMember;
    if (sourceProp.PropertyType.IsGenericType && sourceProp.PropertyType.GetGenericTypeDefinition() == typeof(Nullable<>))
    {
        var hasValue = (bool)sourceProp.PropertyType.GetProperty("HasValue").GetValue(src);
        if (!hasValue)
        {
            return destMember;
        }
    }
    return (int)sourceProp.GetValue(src);
})

问题原因分析

这是AutoMapper 11.x版本的内部优化逻辑导致的冲突:

  • 当谓词表达式**访问了PropertyMap的成员(如SourceType)**时,AutoMapper会标记该谓词为"需要延迟计算",不会提前缓存结果,而是在每个属性映射时重新执行,确保解析器能正确绑定当前属性的上下文,包括目标对象的原有值。
  • 当谓词表达式未访问PropertyMap的任何成员(仅返回true),AutoMapper会触发缓存优化,提前将谓词结果缓存为true,但在此过程中错误地将解析器的destMember参数绑定为default(T),而非实际的目标对象原有属性值。

解决方案

有三种可行的解决方式:

  1. 保留PropertyMap成员访问:即使不需要使用pm.SourceType,也可以添加无意义的访问语句,强制AutoMapper不触发缓存优化:

    cfg.ForAllPropertyMaps(pm => 
    {
        _ = pm.SourceType; // 保留该语句,避免优化逻辑触发
        return true;
    }, (pm, c) => c.ResolveUsing<NullablePreserveResolver<Source, Destination, object>>());
    
  2. 改用ForAllMaps替代ForAllPropertyMaps:ForAllMaps的执行逻辑不会触发上述缓存优化,可确保解析器正确获取目标原有值:

    cfg.ForAllMaps((map, exp) =>
    {
        foreach (var propMap in map.PropertyMaps)
        {
            exp.ForMember(propMap.DestinationMember.Name, opt => 
                opt.ResolveUsing<NullablePreserveResolver<Source, Destination, object>>());
        }
    });
    
  3. 升级AutoMapper版本:该问题在AutoMapper 12.x及以后的版本中已被修复,升级到最新稳定版可直接解决此问题。

内容的提问来源于stack exchange,提问作者redcalx

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.24 22:36:33