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



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

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链接:https://dotnetfiddle.net/F1U9P2

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

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

// 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()提供手动映射方法。可能是这样的:

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中,那么您也可以从上面的类中删除隐式运算符。

更新

由于接口的额外映射,该配置文件将具有所需的行为:

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)映射所有成员,如源代码中所写。

解决方案是在成员映射表达式上配置.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();

最新更新