-->

четверг, 9 июля 2015 г.

AutoMapper: хитрости

Вольный сокращенный перевод понравившегося поста с CodeProject. Рассматриваемые темы:

1. Projection
2. Configuration Validation
3. Custom Conversion
4. Value Resolvers
5. Null Substitution


В отличие от автора, я бы поставил Configuration Validation на первое место по важности. Проверка соответствия свойств класса-результата и класса-источника гарантирует, что любые опечатки, переименования и просто несоответствия имен не останутся незамеченными. Маппинг становится прозрачным и предсказуемым. Но обо всем по порядку:

1. Projection


Допустим, в нашей БД есть таблица Doctor и соответствущая ей модель EF:
public class Doctor
{
    public int Id { get; set; }
    public string Title { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
}
Предположим, нам не нужные полные данные данные из БД, а лишь достаточные для маппинга на поля следующего класса:
public class Person
{
   public string Title { get; set; }
   public string FirstName { get; set; }
   public string LastName { get; set; }
}
Следующий код:
public void DoctorProjectToPerson()
{
   Mapper.CreateMap<Doctor, Person>()
      .ForMember(dest => dest.LastName, opt => opt.Ignore());

   using (MyContext context = new MyContext())
   {
   var person = context.Doctors
      .Project().To<Person>().FirstOrDefault();
   }
}
Приведет к SQL-запросу:
SELECT TOP (1)
   [c].[Id] AS [Id],
   [c].[FirstName] AS [FirstName]
   FROM [dbo].[Doctors] AS [c]
Таким образом, из БД будут получены только поля, участвующие в маппинге <Doctor, Person>.

2. Configuration Validation

//Mapper.CreateMap calls here...
Mapper.AssertConfigurationIsValid();
Тут все просто, метод AssertConfigurationIsValid сгенерирует исключение, если в любом классе-результате маппинга (destination) осталось хотя бы одно не замапленное public свойство.

3. Custom Conversion


Иногда удобно вынести маппинг 2 сильно различающихся классов в отдельный класс:
Mapper.CreateMap<Doctor, HealthcareProfessional>()
   .ConvertUsing<HealthcareProfessionalTypeConverter>();

public class HealthcareProfessionalTypeConverter 
      : ITypeConverter<Doctor, HealthcareProfessional>
{
   public HealthcareProfessional Convert(ResolutionContext context)
   {
      if (context == null || context.IsSourceValueNull)
       return null;
      
      Doctor source = (Doctor)context.SourceValue;
      
      return new HealthcareProfessional
      {
         FullName = string.Join(" ", new[] 
            { source.Title, source.FirstName, source.LastName })
      };
   }
}

4. Value Resolvers


DRY в действии, часто встречающиеся преобразования value-типов можно вынести в отдельный класс:
Mapper.CreateMap<KitchenCutlery, Kitchen>()
   .ForMember(dest => dest.KnifesAndForks, 
      opt => opt.ResolveUsing<KitchenResolver>());

public class KitchenResolver : ValueResolver<KitchenCutlery, int>
{
   protected override int ResolveCore(KitchenCutlery source)
   {
      return source.Knifes + source.Forks;
   }
}

5. Null Substitution


Подставляем значение по-умолчанию, если значение свойства-исходника равно null.
Mapper.CreateMap<Doctor, Person>()
   .ForMember(dest => dest.Title, opt => opt.NullSubstitute("Dr"));

Комментариев нет:

Отправить комментарий