AutoMapper Как сопоставить объект A с объектом B, в зависимости от контекста

Вызов всех гуру AutoMapper!

Я хотел бы иметь возможность сопоставлять объект A с объектом B по-разному в зависимости от контекста во время выполнения. В частности, я хотел бы игнорировать некоторые свойства в одном случае отображения и иметь все свойства, отображаемые в другом случае.

То, что я испытываю, заключается в том, что Mapper.CreateMap может быть успешно вызван в разных случаях сопоставления, однако после вызова CreateMap карта для определенной пары типов устанавливается и впоследствии не изменяется с помощью последующих вызовов CreateMap, которые могут описывают отображение по-разному.

Я нашел сообщение в блоге, которое защищает Mapper.Reset(), чтобы обойти проблему, однако статическая природа класса Mapper означает, что это только вопрос времени, прежде чем произойдет столкновение и сбой.

Есть ли способ сделать это?

Мне кажется, что мне нужно вызвать Mapper.CreateMap один раз на приложение, а позже - вызвать Mapper.Map с подсказками о том, какие свойства должны быть включены/исключены.

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

Каковы мои варианты. Что может быть сделано? Automapper кажется таким многообещающим.

Ответ 1

Класс Mapper - это всего лишь тонкая оболочка поверх объектов Configuration и MappingEngine. Вы можете создавать отдельные экземпляры объектов Configuration/MappingEngine (по-прежнему используя одиночные точки) и использовать свой контейнер IoC для выбора нужного.

Лучшим вариантом является использование разных типов назначения. Очень сложная часть, действительно поддерживающая эту функцию, - это присущая иерархическая природа карт типов. Объект верхнего уровня может иметь профиль сопоставления, а нижний уровень - нет. Некоторые из них могут иметь это или нет и т.д.

Ответ 2

Просто чтобы дополнить Jimmy, ответьте здесь код, необходимый для использования AutoMapper без статического Mapper

Начиная с версии 4.2.1 Automapper имеет санкционированную нестационарную карту и конфигурацию (спасибо Jimmy!).

var config = new MapperConfiguration(cfg => {
    cfg.CreateMap<ClassA, ClassB>();
});

var mapper = config.CreateMapper();

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

(исправить для версии 4.1.1)

// Configuration
AutoMapper.Mappers.MapperRegistry.Reset();
var autoMapperCfg = new AutoMapper.ConfigurationStore(new TypeMapFactory(), AutoMapper.Mappers.MapperRegistry.Mappers);
var mappingEngine = new AutoMapper.MappingEngine(autoMapperCfg);
autoMapperCfg.Seal();

//Usage example
autoMapperCfg.CreateMap<ClassA, ClassB>();

var b = mappingEngine.Map<ClassB>(a);

(правильный для версии 3.2.1)

// Configuration
var platformSpecificRegistry = AutoMapper.Internal.PlatformAdapter.Resolve<IPlatformSpecificMapperRegistry>();
platformSpecificRegistry.Initialize();

var autoMapperCfg = new AutoMapper.ConfigurationStore(new TypeMapFactory(), AutoMapper.Mappers.MapperRegistry.Mappers);
var mappingEngine = new AutoMapper.MappingEngine(autoMapperCfg);

//Usage example
autoMapperCfg.CreateMap<ClassA, ClassB>();

var b = mappingEngine.Map<ClassB>(a);

(правильный для версии 2.2.1)

// Configuration
var autoMapperCfg = new AutoMapper.ConfigurationStore(new AutoMapper.TypeMapFactory(), AutoMapper.Mappers.MapperRegistry.AllMappers());
var mappingEngine = new AutoMapper.MappingEngine(autoMapperCfg);

//Usage example
autoMapperCfg.CreateMap<ClassA, ClassB>();

var b = mappingEngine.Map<ClassB>(a);

Ответ 3

Мне кажется, что лучший дизайн может состоять в том, чтобы иметь несколько целевых классов (возможно, наследуя от общей базы или реализуя общий интерфейс)

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

Например:

public class Source
{
    public string Name {get;set;}
    public BigEntity {get;set;}

    /* other members */
}

public class SourceDTO
{
    public string Name {get;set;}
    public BigEntity {get;set;}
}

public class SourceSummaryDTO
{
    public string Name {get;set;}
}

В качестве альтернативы вы можете сделать это:

public class SourceSummaryDTO : SourceDTO
{
    public string Name {get;set;}
    public BigEntity 
    {
        get{throw new NotSupportedException();}
        set{throw new NotSupportedException();}
    }
}

Таким образом, вы можете передать SourceSummaryDTO, как если бы он был SourceDTO.

Наличие условно заполненных свойств звучит как рецепт проблемы для меня - я бы предпочел, чтобы классы были явными в отношении того, что они содержат, особенно с объектами передачи данных.

Для меня самое лучшее в Automapper - это возможность проверить сопоставления, а затем знать, что каждое свойство в классах назначения будет заполнено.