Comments 19
Для DTO это нормально, а для бизнес-объекта — хуже маппинга вручную.
Resolve
.Можно обойти это ограничение с помощью protected-конструктора и protected setter'ов. Не совсем элегантно, но mapper сможет дотянуться. Поправьте, если я ошибаюсь и через expression такой делегат не скомпилируешь.
Я бы сказал что это ненормально даже для DTO
, я например использую записи в F#
для определения DTO
, кратко и без геммора с поддержкой Equals
и HashCode
. В решениях с max immutability такие подходы c mapper увы, не годятся.
Для быстродействия следующий код должен выполняться один раз, а не в цикле
var key = new TypeTuple(typeof(TSource), typeof(TDest));
var activator = GetMap(key);
т.е. код вызывающий преобразование должен быть расширен для получения оптимального метода преобразования
mapper.Register<CustomerDto, Customer>();
var map = mapper.GetMap<CustomerDto, Customer>();
var customer = map(dto);
Это подходит для того случая, когда перед стоит задача сделать маппинг коллекции здесь и сейчас, однако, ровно такой же код может быть реализован в методе маппинга коллекций самого маппера:
mapper.Map<List<CustomerDTO>, List<Customer>>()
В поле класса который этот маппер использует.
ObjectBuilder.GetActivator<TSource, TDest>()
Ничего не имею против такого подхода, но в таком случае вам в качестве зависимостей нужен не столько маппер, сколько библиотека по компиляции плана создания экземпляра требуемого типа.Еще пара замечаний по дизайну.
- Сейчас маппер имеет изменяемое состояние.
Логично разделить его на билдер (где можно делать регистрации) и маппер (потокобезопасный объект без состояния, можно только использовать ранее зарегистрированное). - Сейчас маппер имеет неопределенный интерфейс (эквивалент локатора сервисов) — результат маппинга определяется не контрактом, а ранее сделанными регистрациями.
Логично сделать не единый маппер с множеством регистраций и обобщенными методами, а обобщенный маппер (каждый на одну пару типов). Такой дизайн позволит полностью избавиться от изменяемого состояния (все что сейчас делается при регистрации можно будет реализовать прямо в конструкторе) и сделать зависмосоти от маппинга прозрачными (сразу видны требуемые типы).
2. Не совсем понял. Можно пояснения в виде кода?
Я правильно понимаю, что Ваш маппер НЕ поддерживает:
- маппинг вложенных свойств
- маппинг коллекций
- маппинг на существующий объект (обновление)
- генерацию проекций для
IQueryable<T>
И при этом (судя по Вашим бенчмаркам) всего на 10% бытрее того же Mapster, который все это умеет?
Еще было бы неплохо сравнить с EmitMapper.
А для ускорения маппинга вместо Expression.Compile
можно использовать FastExpressionCompiler.
Суть проекта в компиляции expression tree напрямую, без создания анонимной assembly для запуска в песочнице (как это делает Expression.Compile
).
В первую очередь было интересно реализовать совершенно базовый функционал наиболее оптимальным способом.
Добавлю EmitMapper в сравнение и попробую FastExpressionCompiler. Спасибо.
В первую очередь было интересно реализовать совершенно базовый функционал наиболее оптимальным способом.
Написание готового к продакшину маппера задача крайне нетривиальная и требует очень хорошо знания дотнета и всех его gotchas, либо накопления достаточного опыта в процессе постепенной разработки и фидбека пользователей. Это проект скорее POC как быстрее всего вызвать конструктор, чем готовый к использованию где либо набор базового функционала для маппера. Задача маппера удобный и понятный апи, универсальность(множество сфер применения), кастомизируемость, позволяющая избегать дубликатов кода, удобная расширяемость — известные на рынке вендоры годами накапливают опыт работы с ошибками, переиспользования конфигураций, добавления точек расширяемость, обработки частных случаев и в конце концов ускорения работы.
Использование Expression для движка и кеширования для ускорения это пожалуй одни из самых банальных и тривиальных вещей, с которыми стакливаються нынче разработчики мапперов.
Единожды была необходимость для одного из крупных корп заказчиков написать и развивать подобный проект на протяжении нескольких лет для множества проектов(причина созданий — вопрос бюрократии), который заменял automapper/dapper — сделать его соизмеримо быстрым по функционалу в соотвествующих задачах было куда проще — чем думать прорабатывать апи и решать описанные выше проблемы — это то, что требует длительного накопления опыта ииспользования в продакшине.
Сейчас на рынке достаточно удобных мапперов я стараюсь всегда использовать их для своих задач — ко всему остальному отношусь скептически, идея написания маппере для многих люди одна из самых простых где очень многие пытаются просто сделать свой велосипед аппелируя к скорости.
System.MissingMethodException: Method not found: 'System.Reflection.Emit.AssemblyBuilder System.AppDomain.DefineDynamicAssembly(System.Reflection.AssemblyName, System.Reflection.Emit.AssemblyBuilderAccess)'
Ну и необходимость лезть в исходники, чтобы понять, что скрывается за именами в бенчмарках — за гранью бобра и козла.
А почему бы не написать генератор мапинговых методов на Roslyn (для нас ведь главное уйти от написания рутинного кода)? Это сэкономит время старта, добавит гибкость (расширять мапинг можно будет обычным C# кодом) и можно даже сделать проверку вызова незарегистрированных мапингов при компиляции.
Пишем свой маппер для .NET Standard 2.0