Automapper: использование преобразователя типов и преобразователей значений для одних и тех же типов
У меня есть много 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
1 ответ
Вы говорите AutoMapper конвертировать все десятичные-> десятичные отображения, используя MoneyConverter
, AutoMapper фактически использует ваш преобразователь (установите точку останова, чтобы увидеть его), но результатом разрешения является десятичное значение, которое затем используется MoneyConverter
Вы подали заявку.
Примечание: похоже, это дизайн AutoMapper; Я не вижу способа переопределить конвертер типов.
Не все ваши десятичные свойства представляют деньги, поэтому вы можете использовать другой подход. Также спросите себя, вы округляете чисто значения, используемые для представления, или вы потенциально теряете точность, которую хотели бы сохранить в этих объектах домена? Если вы действительно нуждаетесь в округлении, вы можете указать явное значение для каждого члена, пропуская конвертер полностью. Кроме того, вы можете игнорировать элементы, которые являются исключениями, и обрабатывать это с .ConstructUsing(...)
,
Но поскольку ваш первоначальный вопрос касается использования Resolver и Converter для одного и того же типа, вот один из способов заставить его работать:
По сути, мы хотим, чтобы конвертер пропустил конвертацию по умолчанию для определенных свойств. Мы не можем сделать это с помощью конфигурации, поэтому нам придется делать это во время выполнения. Предполагая, что у вас есть доступ к 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 на основе атрибутов и полностью пропустите преобразователи.