AutoMapper 配置为忽略空源成员到自定义结构类型目标成员

AutoMapper configure to ignore null source member to custom struct type destination member

我有两个 类,一个接口和一个自定义结构,如下所示:

public class Source
{
    public decimal? Prop { get; set; }
}

public class Destination : IDestination
{
    public Destination()
    {
        Prop = new MyStruct(10);
    }
    public MyStruct Prop { get; set; }
}

public interface IDestination
{
    MyStruct Prop { get; set; }
}

public struct MyStruct
{
    public decimal Value { get; private set; }
    public MyStruct(decimal value)
    {
        Value = value;
    }
    public static implicit operator MyStruct(decimal val) => new MyStruct(val);
}

我想要实现的是在从 Source 映射到 Destination 时忽略 Prop 的任何空值。例如,

var obj1 = mapper.Map<IDestination>(new Source());
// obj1.Prop should be MyStruct(10), because null value in Source should be ignored

var obj2 = mapper.Map<IDestination>(new Source() { Prop = 20 });
// obj2.Prop should be MyStruct(20), because Prop is 20m so it is mapped

我现在所拥有的没有按预期工作:

// I have this conversion pair in a few classes so the mapping is not at the member level
CreateMap<decimal?, MyStruct>()
    .ConvertUsing((source, dest) => source ?? dest); // Use the destination value when source is null
CreateMap<Source, IDestination>()
    .ConstructUsing((_, _) => new Destination()) // This is actually constructed using the service locator but for simplicity I am using the concrete class constructor
    .ForAllMembers(options => options.Condition((_, _, sourceMember) =>
    {
        return sourceMember != null; // Only allow mapping of non null members
    }));

Fiddle link: https://dotnetfiddle.net/F1U9P2

是否有正确的方法来配置 AutoMapper,使其忽略空值到非空值的映射属性?

编辑: 在进一步测试中,我发现

// This works
var obj1 = mapper.Map<Source, IDestination>(new Source(), new Destination());
// This does not work (when destination object is created by ConstructUsing)
var obj1 = mapper.Map<IDestination>(new Source());

您还应该提供从 decimal?MyStruct 的显式映射,并使用 .ConvertUsing() 提供手动映射方法。可能 something like this:

public class MyProfile : Profile
{
    public MyProfile()
    {
        CreateMap<Source, Destination>();
        CreateMap<decimal?, MyStruct>()
            .ConvertUsing((source, dest, context) =>
            {
                return source.HasValue
                    ? new MyStruct(source.Value)
                    : dest;
            });
    }
}

由于我们采用原始值或创建 new MyStruct(source.Value) 的事实,如果唯一使用的位置在 AutoMapper 中,您也可以从上面的 class 中删除隐式运算符。

更新

由于来自界面的额外映射,此配置文件将具有 desired behaviour:

public class MyProfile : Profile
{
    public MyProfile()
    {
        CreateMap<Source, IDestination>()
            .ConvertUsing((source, dest, context) => {
                dest ??= new Destination();

                if(source.Prop.HasValue)
                    dest.Prop = source.Prop.Value;

                return dest;
            });
    }
}

在 AutoMapper 中,当使用服务定位器创建目标对象时,它将初始目标视为 null,并在未配置 UseDestinationValue 时使用默认值 default(MyStruct) 映射所有成员成员,如 source code.

中所写

解决方案是在成员映射表达式上配置 .UseDestinationValue()

在我的例子中,有许多模型的成员应该从 decimal? 映射到 MyStruct,所以我有一个扩展方法。

public static IMappingExpression<TSource, TDestination> UseDestinationValueForKnownMemberTypes<TSource, TDestination>(this IMappingExpression<TSource, TDestination> mappingExpression)
{
    mappingExpression.ForAllMembers(options =>
    {
        if (IsKnownType(options.DestinationMember))
        {
            options.UseDestinationValue();
        }
    });
    return mappingExpression;
}

private static bool IsKnownType(MemberInfo destinationMember)
{
    var propertyType = destinationMember is PropertyInfo p ? p.PropertyType : null;
    // Use a static hashset to handle multiple types 
    return propertyType == typeof(MyStruct);
}

CreateMap<Source, IDestination>()
    .UseDestinationValueForKnownMemberTypes();