我有很多必须映射到域名对象的DTO。通常,映射到货币值需要应用舍入规则。这适用于95%以上的案例,但我有一些数据需要不同的舍入规则。我打算做下一个:
1)为常规舍入规则创建一个ITypeConverter,适用于此类型转换的默认值。
2)为特殊的舍入情况创建ValueResolver,并在成员映射中指定它们的用法。
我创建了一个测试用例来尝试我的方法,但在针对特殊情况应用ValueResolver之后,AutoMapper使用TypeConverter来获取最终值,使用一般规则。
这是一个错误的方法?可能是我错过了什么?
然后是测试用例:
[TestFixture]
public class RoundingTests
{
public class MoneyConverter : ITypeConverter<decimal, decimal>
{
public decimal Convert(ResolutionContext context)
{
return Math.Round((decimal)context.SourceValue, 2, MidpointRounding.AwayFromZero);
}
}
public class Money12Resolver : ValueResolver<decimal, decimal>
{
protected override decimal ResolveCore(decimal source)
{
return Math.Round(source, 12, MidpointRounding.AwayFromZero);
}
}
public class PercentResolver : ValueResolver<decimal, decimal>
{
protected override decimal ResolveCore(decimal source)
{
return Math.Round(source, 4, MidpointRounding.AwayFromZero);
}
}
internal class Source
{
public decimal Price { get; set; }
public decimal Percent { get; set; }
public decimal Prorate { get; set; }
}
internal class Destination
{
public decimal Price { get; set; }
public decimal Percent { get; set; }
public decimal Prorate { get; set; }
}
[Test]
public void MappingTest()
{
Mapper.CreateMap<decimal, decimal>().ConvertUsing<MoneyConverter>();
Mapper.CreateMap<Source, Destination>()
.ForMember(d => d.Percent, o => o.ResolveUsing<PercentResolver>().FromMember(s => s.Percent))
.ForMember(d => d.Prorate, o => o.ResolveUsing<Money12Resolver>().FromMember(s => s.Prorate));
Mapper.AssertConfigurationIsValid();
var source = new Source
{
Price = 12345.6789m,
Percent = 0.123456m,
Prorate = 123.123451234512345m
};
var convertion = Mapper.Map<Destination>(source);
Assert.That(convertion, Is.Not.Null);
Assert.That(convertion.Price, Is.EqualTo(12345.68m));
Assert.That(convertion.Percent, Is.EqualTo(0.1235m));
Assert.That(convertion.Prorate, Is.EqualTo(123.123451234512m));
}
}
测试结果:
Expected: 0.1235m
But was: 0.12m
答案 0 :(得分:0)
您告诉AutoMapper使用MoneyConverter
转换所有小数&gt;小数点映射。事实上,AutoMapper会使用您的解析器(设置断点来查看),但解析的结果是一个十进制值,然后由您应用的MoneyConverter
使用。
注意: 这似乎是AutoMapper的设计;我没有看到覆盖类型转换器的方法。
并非所有小数属性都代表金钱,因此您可能希望采用不同的方法。还要问问自己,你是纯粹为了用于演示的值而四舍五入,还是你可能会失去你想要保留在那些域对象中的精度?如果你真的需要舍入,你可以明确并为每个成员设置一个解析器,完全跳过转换器。或者,您可以忽略作为例外的成员并使用.ConstructUsing(...)
处理该成员。
但由于您的原始问题与使用相同类型的旋转变压器和转换器相关,因此您可以通过以下方式使其工作:
基本上我们希望转换器跳过某些属性的默认转换。我们无法通过配置来完成,因此我们必须在运行时执行此操作。假设您可以访问Destination
类,只需使用自定义属性修饰具有非默认行为的属性。
class PercentAttribute : Attribute
{ }
class Destination
{
[Percent]
public decimal Percent { get; set; }
...
}
在您的转换器中,您可以查找[Percent]
的属性并返回来自PercentResolver
的源值。
public class MoneyConverter : ITypeConverter<decimal, decimal>
{
public decimal Convert(ResolutionContext context)
{
var propInfo = context.PropertyMap.DestinationProperty.MemberInfo;
bool isPercent = propInfo.GetCustomAttributes(typeof(PercentAttribute), true).Any();
if (isPercent) return (decimal)context.SourceValue;
return Math.Round((decimal)context.SourceValue, 2, MidpointRounding.AwayFromZero);
}
}
如果你要去那条路线,只需让转换器根据属性进行Money,Percent和Money12转换,然后完全跳过解析器。