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();
我有两个 类,一个接口和一个自定义结构,如下所示:
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();