Pull to refresh

Comments 18

А среди аналогов AutoMapper точно не нашлось ничего подходящего?
Даже беглый поиск по гитхабу выдает кучу либ:


Да, я искал аналогичные либы. ExpressMapper, Mapster, еще TinyMapper гугл выдает, они все имеют абсолютно аналогичный синтаксис создания маппинга автомапперу. Проблемы, которые у меня возникали при работе с автомаппером, я в той или иной степени решал допиливая для него небольшие расширения, но именно идея более простого синтаксиса сподвигла меня на написание собственной либы. Сравните конфигурацию маппинга для двух классов, у которых отличаются два поля:

AutoMapper
configuration.CreateMap<Person, PersonViewModel>()
    .ForMember(vm => vm.FullName, 
        o => o.MapFrom(p => $"{p.FirstName} {p.LastName}"))
    .ForMember(vm => vm.Age, 
        o => o.MapFrom(p => Helpers.CalculateAge(p.BirthDate)))
    .ForMember(vm => vm.Foo, o => o.Ignore())
    .ForMember(vm => vm.Bar, o => o.UseValue(5))


FlashMapper
mappingConfiguration.CreateMapping<Person, PersonViewModel>(
    p => new PersonViewModel
    {
        FullName = $"{p.FirstName} {p.LastName}",
        Age = Helpers.CalculateAge(p.BirthDate),
        Bar = 5,
        Foo = MappingOptions.Ignore()
    });



Данный код не только более компактный, но и более дружественен к интелисенсу.
Вы абсолютно не учли, что подобный подход к построению конфигурации в AutoMapper позволяет до-определить конфигурацию отражения, и делать такие штуки:

configuration.CreateMap<Person, PersonViewModel>()
    .ForMember(vm => vm.FullName, 
        o => o.MapFrom(p => $"{p.FirstName} {p.LastName}"))
    .ForMember(vm => vm.Age, 
        o => o.MapFrom(p => Helpers.CalculateAge(p.BirthDate)))
    .ForMember(vm => vm.Foo, o => o.Ignore())
    .ForMember(vm => vm.Bar, o => o.UseValue(5))
    // вызываем собственное расширение, которое расширяет конфигурацию
    .ForEntityToDtoCommon(); // собственное расширение
    // плюс ещё какие-нибудь...


Ну и MapFrom это отражение, которое может транслироваться в SQL, а ваш new PersonViewModel — не может, и сделать это будет крайне затруднительно. Хотя, если получится, то будет даже круто. Но не забывайте про необходимость до-определения, это нужно!

FlashMapper использует только Expression<Func> из-за чего невозможно для преобразования вызывать другие методы со сложной логикой. :(

И еще есть такой замечательный проект для микробенчмарков: BenchmarkDotNet. Де-факто, стандарт для измерения производительности.

Довольно бегло пролистал вашу статью, но не нашел толком информации по вложенным маппингам, а так же поддержки выражений для селекта из DB-провайдеров.
В автомаппере чаще всего использую именно что то в роде
db.Users.GetAll<User>().ProjectTo<UserViewModel>()
и автомаппер автоматом подставляет нужное ваыражение, чтобы провайдер забрал только нужные поля из базы.

Вложенных маппингов нет. Если честно, он пока даже простые типы не умеет автоматически преобразовывать друг к другу, например, из числа в строку. В какой-то степени это вытекает из синтаксиса конфигурации. Если названия свойств совпадают, то там еще можно автоматом сопоставить, но если различаются, и вам нужно будет их явно связать, то вам просто компилятор не даст присвоить одно в другое, если у них будут отличаться типы.
Аналога ProjectTo тоже нет, но согласен, что функция это очень интересная, постараюсь ее реализовать в будущем.

А маппинг названий ведь регистронезависимый? А как происходит маппинг свойств, тип которых — коллекция? А есть ли поддержка кастомного маппинга определенного типа?

Мне бы хотелось найти не способ уместить тот же самый маппинг в меньшее количество строк — а способ вынести часть правил маппинга в конвенции.

Чтобы можно было что-то наподобие «если у родителя есть свойство типа `List`, где T — наследник ModelBase, то при отображении его свойств тип Guid может маппиться на T таким-то ресолвером».

upd: List<T>, когда же галочка "Markdown" перестанет пропадать сама собой...

Маппинг по конвенциям, звучит как задача для EmitMapper

Прежде всего — хвалю за проделанную работу, всегда хорошо что человек пробует что-то новое.

НО,
Во-первых, можете полить меня кипятком, но для сложных случаях — где сокращение синтаксиса от обычного создания нового экземпляра и перенос свойств вручную?
Во-вторых, очень уж я боюсь судьбы фронтенда для .NET, что начнет появляться громадное количество микро-фреймворков просто потому что «хотелось чуть перламутровее».
В-третьих, сама по себе идея автомаппинга сильно различающихся сущностей мне кажется порочной.

П.С. Извиняюсь дико, но не «Dependancy» а «Dependency».
По первому пункту.

Заголовок спойлера
mappingConfiguration.CreateMapping<Person, PersonViewModel>(
    p => new PersonViewModel
    {
        FullName = $"{p.FirstName} {p.LastName}",
        Age = Helpers.CalculateAge(p.BirthDate),
        Bar = 5,
        Foo = MappingOptions.Ignore()
    });


Этот код только выглядит как создание нового экземпляра и перенос свойств вручную. На деле это лямбда-выражение, из которого компилятор c# генерит ExpressionTree, из которого в свою очередь можно получить всю нужную информацию для создания маппинга. В примере сопоставляются только различающиеся свойства. Идентичные свойства из обоих классов сопоставятся автоматически.

Да уж, жестко я опечатался, прямо в названии проекта, сборки и пакета.
Вы — молодец, синтаксис, действительно, выглядит получше, чем в AutoMapper!
Эмм…

В автомаппере мне никогда не нравился его монструозный синтаксис. Просто, чтобы связать два свойства в разных классах нужно прописать три лямбды в конфигурации, чтобы проигнорировать свойство — две.


А что, написать расширение к AutoMapper, «включающий» необходимый и удобный вам синтаксис определений отражения — не? Он же расширяемый вдоль и поперёк. Можно даже авторегистрацию маппинга приделать на атрибутах.

Кроме того, без проекций (ProjectTo) любой маппер является бесполезной игрушкой на выброс. Только с проекциями можно избежать проблемы SELECT N+1, за что так любят ругать ORM-ы. Если нет проекций, такой маппер бесполезен.
А мне вот всегда было интересно, насколько вообще AutoMapper и аналоги применимы в больших долгосрочных проектах? Когда код больше читаешь, чем пишешь.

Самая большая проблема – перестает работать «find usages». И ты начинаешь мучительно искать, кто же и где пишет в это конкретное поле.

А еще часто появляются либо зубодробительные конфигурации кастомных маппингов (с чем автор и борется в статье). Либо методы, в которых заполняются поля объекта, которые не смаппились по-умолчанию.

Все это очень запутывает =(
mappingConfiguration.Convert(model).To<UserDb>();
можно сократить до
mappingConfiguration.ConvertTo<UserDb>(model);

Хотя это можно сделать и расширением класса.
Sign up to leave a comment.

Articles