Введение
Данная статья предназначена к прочтению разработчикам и архитекторам распределенных систем на платформе .NET. В ней будет рассмотрен гибкий каркас для объектно-объектного преобразования (далее маппинга). Так же будут рассмотрены некоторые аспекты
Domain-Driven Design’а.
Зачем мне нужен объектно-объектный маппинг?
Следуя основным принципам
DDD, мы реализуем так называемую
Rich Domain Model (эти объекты также должны соответствовать принципу
POxO). Объекты реального мира, нашедшие отражение в нашем приложении частенько так же передают достаточную сложность, следовательно, достаточно корректно построенная модель крайне тяжело поддаётся перемещению между слоями приложения (не путать с легкостью вносимых изменений).

Тем не менее достаточно часто появляется необходимость “распределения” (я имею ввиду создание промежуточных сущностей, а не растекание модели по слоям) модели между слоями, для отображения, к примеру, атрибутов её сущностей пользователям (в
шаблонах представления MVx), а так же передаче по сервисам (
Data Transfer Object). Порой бывает даже, что модель “распределяется” для тестирования некоторых аспектов. Предположим, мы в Африке, у нас банановая плантация, всё классно, выращиваем, продаём, выращиваем, продаём, но тут внезапно внутренний рынок переполняется и нам надо расширятся (к примеру вести бананы в Россию), мы пишем
WCF сервис, который будет слать наши бананы. Так как бананы в Африке имеют несколько иное значение, чем в России, то, соответственно нам понадобятся лишь некоторые атрибуты (остальные фактически не имеют значения), которые мы забубеним в наш DTO
Правильнее было бы дать классу BananaWrapper название BananaDTO, для того, чтобы точно отображать его функциональное назначение, но я оставлю название таким для большего уровня абстракции, к примеру, если нам понадобится сделать автомат по продажи бананов и поместить этот объект в Presenter Model
Хочу заметить, что порой задача преобразования объектов становится довольно-таки нетривиальной и в лучшем случаем выглядит примерно подобным образом (это решение в лоб, есть ещё более изощренные методы ;)):
- public class Banana
- {
- public string Country { get; set; }
- public double Price { get; set; }
- public double NationalTax { get; set; }
- public bool CocaineInjection { get; set; }
- public bool FreshForFooding { get; set; }
- public string AkunamatataName { get; set; }
- public string BananaGeneration { get; set; }
- public int Age { get; set; }
- }
-
- public class BananaWrapper
- {
- public string Country { get; set; }
- public double Price { get; set; }
- public int Age { get; set; }
- }
- public class BananaMapper
- {
- public BananaWrapper GetWrapper(Banana banana)
- {
- return new BananaWrapper
- {
- Country = banana.Country,
- Price = banana.Price,
- Age = banana.Age
- };
- }
- }
* This source code was highlighted with Source Code Highlighter.
Думаю, что такой код писать, а тем более сопровождать, мало кому будет в радость, в последнее время я как раз частенько встречался с такого рода задачами, и находился в поиске решения проблемы.
AutoMapper
И тут на сцену выходит наш персонаж –
AutoMapper, и сразу же говорит мне: — послушай, ты что такое пишешь? тебе не лень? ты не боишься допустить ошибок? хочешь я тебе помогу?!.. Я конечно же соглашаюсь, и получаю в ответ следующее решение моей проблемы:
- public class BananaMapper
- {
- public BananaMapper()
- {
- Mapper.CreateMap<Banana, BananaWrapper>();
- }
-
- public BananaWrapper GetWrapper(Banana banana)
- {
- return Mapper.Map<Banana, BananaWrapper>(banana); ;
- }
- }
* This source code was highlighted with Source Code Highlighter.
Класс, и это действительно всё, что мне понадобится. Сложность, вышележащего примера снизилась в моих глазах до нуля. Итак, что же за механизмы лежат внутри AutoMapper? AutoMapper проверяет есть соответствующие поля в указанных типах, соответствие проводится как по имени свойства, так и по его типу. Даже такие нюансы, как Product.Name и ProductName будут учтены и обработаны автоматически (wow!). Плюс ко всему методы GetXXX() будут ложится на свойства XXX (да, ну и естественно для особо раздражительных все эти прелести можно отключить и переопределить всё в своих собственных таблицах соответствия (далее мапках)). Кастомная конфигурация выглядит примерно следующим образом:
- Mapper.CreateMap<CalendarEvent, CalendarEventForm>()
- .ForMember(dest => dest.EventDate, opt => opt.MapFrom(src => src.EventDate.Date))
- .ForMember(dest => dest.EventHour, opt => opt.MapFrom(src => src.EventDate.Hour))
- .ForMember(dest => dest.EventMinute, opt => opt.MapFrom(src => src.EventDate.Minute));
* This source code was highlighted with Source Code Highlighter.
Кстати, все ваши кастомные конфигурации легко поддаются проверке с помощью следующего метода:
- Mapper.AssertConfigurationIsValid();
* This source code was highlighted with Source Code Highlighter.
Так же не плохо работает с:
История
Проект появился в конце’08-начале’09, около полугода находился в версии 0.31, сейчас же добрался до RC 1.0, думаю, что релиз уже совсем скоро.
Overhead?

Дебаты по поводу того, насколько быстрее будет работать AutoMapper и присвоение свойств в ручную (и прочие мульки) я игнорирую, т.к. готов пойти на любые жертвы производительности, если получу ясный, читабельный код. Ах, да, автор AutoMapper позаботился об этих вопросах и написал бенчмарки, смотреть здесь:
http://code.google.com/p/automapperhome/source/browse/#svn/trunk/src/Benchmark Ресурсы
Скачать проект, а так же ознакомиться с исходными кодами можно здесь:
http://code.google.com/p/automapperhome/ Обсуждения каркаса здесь:
http://groups.google.com/group/automapper-users Так же примеры использования есть здесь:
http://automapper.codeplex.com/ Кстати проект разрабатывает
Joe Benninghoven, который так же пишет
BDD фреймворк для .NET под названием
NBehave.