AutoMapper: "Игнорировать все остальное"?

Есть ли способ сказать AutoMapper игнорировать все свойства, кроме тех, которые отображаются явно?

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

23 ответа

Решение

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

public static IMappingExpression<TSource, TDestination> IgnoreAllNonExisting<TSource, TDestination>
(this IMappingExpression<TSource, TDestination> expression)
{
    var flags = BindingFlags.Public | BindingFlags.Instance;
    var sourceType = typeof (TSource);
    var destinationProperties = typeof (TDestination).GetProperties(flags);

    foreach (var property in destinationProperties)
    {
        if (sourceType.GetProperty(property.Name, flags) == null)
        {
            expression.ForMember(property.Name, opt => opt.Ignore());
        }
    }
    return expression;
}

Использование:

Mapper.CreateMap<SourceType, DestinationType>()
                .IgnoreAllNonExisting();

ОБНОВЛЕНИЕ: Очевидно, это не работает правильно, если у вас есть пользовательские сопоставления, потому что они перезаписывают их. Я думаю, что это все еще может работать, если сначала вызвать IgnoreAllNonExisting, а затем - пользовательские сопоставления.

У schdr есть решение (как ответ на этот вопрос), которое использует Mapper.GetAllTypeMaps() чтобы выяснить, какие свойства не сопоставлены, и автоматически игнорировать их. Похоже, более надежное решение для меня.

Из того, что я понял, вопрос заключался в том, что в месте назначения есть поля, которые не имеют сопоставленного поля в источнике, поэтому вы ищете способы игнорировать эти не сопоставленные поля назначения.

Вместо того, чтобы реализовывать и использовать эти методы расширения, вы можете просто использовать

Mapper.CreateMap<destinationModel, sourceModel>(MemberList.Source);  

Теперь автопроизводитель знает, что ему нужно только проверить, что все исходные поля сопоставлены, но не наоборот.

Я обновил расширение Can Gencer, чтобы не переписывать существующие карты.

public static IMappingExpression<TSource, TDestination> 
    IgnoreAllNonExisting<TSource, TDestination>(this IMappingExpression<TSource, TDestination> expression)
{
    var sourceType = typeof (TSource);
    var destinationType = typeof (TDestination);
    var existingMaps = Mapper.GetAllTypeMaps().First(x => x.SourceType.Equals(sourceType) && x.DestinationType.Equals(destinationType));
    foreach (var property in existingMaps.GetUnmappedPropertyNames())
    {
        expression.ForMember(property, opt => opt.Ignore());
    }
    return expression;
}

Использование:

Mapper.CreateMap<SourceType, DestinationType>()
                .ForMember(prop => x.Property, opt => opt.MapFrom(src => src.OtherProperty))
                .IgnoreAllNonExisting();

Версия 5.0.0-бета-1 AutoMapper представляет ForAllOtherMembers метод расширения, так что теперь вы можете сделать это:

CreateMap<Source, Destination>()
    .ForMember(d => d.Text, o => o.MapFrom(s => s.Name))
    .ForMember(d => d.Value, o => o.MapFrom(s => s.Id))
    .ForAllOtherMembers(opts => opts.Ignore());

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

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

Я смог сделать это следующим образом:

Mapper.CreateMap<SourceType, DestinationType>().ForAllMembers(opt => opt.Ignore());
Mapper.CreateMap<SourceType, DestinationType>().ForMember(/*Do explicit mapping 1 here*/);
Mapper.CreateMap<SourceType, DestinationType>().ForMember(/*Do explicit mapping 2 here*/);
...

Примечание: я использую AutoMapper v.2.0.

Начиная с AutoMapper 5.0, .TypeMap собственность на IMappingExpression ушел, это означает, что решение 4.2 больше не работает. Я создал решение, которое использует оригинальную функциональность, но с другим синтаксисом:

var config = new MapperConfiguration(cfg =>
{
    cfg.CreateMap<Src, Dest>();
    cfg.IgnoreUnmapped();        // Ignores unmapped properties on all maps
    cfg.IgnoreUnmapped<Src, Dest>();  // Ignores unmapped properties on specific map
});

// or add  inside a profile
public class MyProfile : Profile
{
   this.IgnoreUnmapped();
   CreateMap<MyType1, MyType2>();
}

Реализация:

public static class MapperExtensions
{
    private static void IgnoreUnmappedProperties(TypeMap map, IMappingExpression expr)
    {
        foreach (string propName in map.GetUnmappedPropertyNames())
        {
            if (map.SourceType.GetProperty(propName) != null)
            {
                expr.ForSourceMember(propName, opt => opt.Ignore());
            }
            if (map.DestinationType.GetProperty(propName) != null)
            {
                expr.ForMember(propName, opt => opt.Ignore());
            }
        }
    }

    public static void IgnoreUnmapped(this IProfileExpression profile)
    {
        profile.ForAllMaps(IgnoreUnmappedProperties);
    }

    public static void IgnoreUnmapped(this IProfileExpression profile, Func<TypeMap, bool> filter)
    {
        profile.ForAllMaps((map, expr) =>
        {
            if (filter(map))
            {
                IgnoreUnmappedProperties(map, expr);
            }
        });
    }

    public static void IgnoreUnmapped(this IProfileExpression profile, Type src, Type dest)
    {
        profile.IgnoreUnmapped((TypeMap map) => map.SourceType == src && map.DestinationType == dest);
    }

    public static void IgnoreUnmapped<TSrc, TDest>(this IProfileExpression profile)
    {
        profile.IgnoreUnmapped(typeof(TSrc), typeof(TDest));
    }
}

Для Automapper 5.0, чтобы пропустить все несопоставленные свойства, вам просто нужно положить

.ForAllOtherMembers(х =>x.Ignore());

в конце вашего профиля.

Например:

internal class AccountInfoEntityToAccountDtoProfile : Profile
{
    public AccountInfoEntityToAccountDtoProfile()
    {
        CreateMap<AccountInfoEntity, AccountDto>()
           .ForMember(d => d.Id, e => e.MapFrom(s => s.BankAcctInfo.BankAcctFrom.AcctId))
           .ForAllOtherMembers(x=>x.Ignore());
    }
}

В этом случае будет разрешено только поле Id для выходного объекта, все остальные будут пропущены. Работает как шарм, кажется, нам больше не нужны хитрые расширения!

Прошло несколько лет с тех пор, как вопрос был задан, но этот метод расширения кажется мне чище, используя текущую версию AutoMapper (3.2.1):

public static IMappingExpression<TSource, TDestination> IgnoreUnmappedProperties<TSource, TDestination>(this IMappingExpression<TSource, TDestination> expression)
{
    var typeMap = Mapper.FindTypeMapFor<TSource, TDestination>();
    if (typeMap != null)
    {
        foreach (var unmappedPropertyName in typeMap.GetUnmappedPropertyNames())
        {
            expression.ForMember(unmappedPropertyName, opt => opt.Ignore());
        }
    }

    return expression;
}

Для тех, кто использует нестатический API в версии 4.2.0 и выше, используйте следующий метод расширения (находится здесь в AutoMapperExtensions класс) можно использовать:

// from http://stackru.com/questions/954480/automapper-ignore-the-rest/6474397#6474397
public static IMappingExpression IgnoreAllNonExisting(this IMappingExpression expression)
{
    foreach(var property in expression.TypeMap.GetUnmappedPropertyNames())
    {
        expression.ForMember(property, opt => opt.Ignore());
    }
    return expression;
}

Здесь важно то, что после удаления статического API такой код, как Mapper.FindTypeMapFor больше не будет работать, следовательно, использование expression.TypeMap поле.

Я обновил ответ Роберта Шредера для AutoMapper 4.2. С нестатическими конфигурациями сопоставления мы не можем использовать Mapper.GetAllTypeMaps(), но expression имеет ссылку на необходимый TypeMap:

public static IMappingExpression<TSource, TDestination> 
    IgnoreAllNonExisting<TSource, TDestination>(this IMappingExpression<TSource, TDestination> expression)
{
    foreach (var property in expression.TypeMap.GetUnmappedPropertyNames())
    {
        expression.ForMember(property, opt => opt.Ignore());
    }
    return expression;
}

Как бы вы предпочли указать, что некоторые члены игнорируются? Есть ли соглашение, базовый класс или атрибут, который вы хотели бы применить? Когда вы начнете явно указывать все сопоставления, я не уверен, какую ценность вы получите от AutoMapper.

Это кажется старым вопросом, но я решил опубликовать свой ответ для всех, кто похож на меня.

Я использую ConstructUsing, инициализатор объекта в сочетании с ForAllMembers игнорировать, например

    Mapper.CreateMap<Source, Target>()
        .ConstructUsing(
            f =>
                new Target
                    {
                        PropVal1 = f.PropVal1,
                        PropObj2 = Map<PropObj2Class>(f.PropObj2),
                        PropVal4 = f.PropVal4
                    })
        .ForAllMembers(a => a.Ignore());

By default, AutoMapper uses the destination type to validate members but you can skip validation by using MemberList.None option.

      var configuration = new MapperConfiguration(cfg =>
  cfg.CreateMap<Source2, Destination2>(MemberList.None);
);

You can find the reference here

In a WebApi for dotnet 5, using the Nuget package AutoMapper.Extensions.Microsoft.DependencyInjection, I'm doing it like this in a mapper profile. I'm really rusty with AutoMapper, but it seems to work fine now for unmapped members.

In Startup:

      var mapperConfig = new MapperConfiguration(mc => mc.AddProfile(new AutoMapperProfile()));
    services.AddSingleton(mapperConfig.CreateMapper());

and in my AutoMapperProfile:

      CreateMap<ProjectActivity, Activity>()
        .ForMember(dest => dest.ActivityName, opt => opt.MapFrom(src => src.Name))
        .ValidateMemberList(MemberList.None);

Я знаю, что это старый вопрос, но @jmoerdyk в вашем вопросе:

Как бы вы использовали это в цепочечном выражении CreateMap() в профиле?

Вы можете использовать этот ответ, как это внутри профиля Ctor

this.IgnoreUnmapped();
CreateMap<TSource, Tdestination>(MemberList.Destination)
.ForMember(dest => dest.SomeProp, opt => opt.MapFrom(src => src.OtherProp));

Единственная информация о игнорировании многих участников - эта тема - http://groups.google.com/group/automapper-users/browse_thread/thread/9928ce9f2ffa641f. Я думаю, что вы можете использовать трюк, используемый в ProvidingCommonBaseClassConfiguration, чтобы игнорировать общие свойства для похожих классов.
И нет никакой информации о функционале "Игнорировать остальные". Я смотрел на код раньше, и мне кажется, что добавить такую ​​функциональность будет очень и очень сложно. Также вы можете попробовать использовать какой-либо атрибут и пометить им игнорируемые свойства и добавить общий / общий код, чтобы игнорировать все отмеченные свойства.

Вы можете использовать ForAllMembers, чем нужно перезаписывать только так

public static IMappingExpression<TSource, TDest> IgnoreAll<TSource, TDest>(this IMappingExpression<TSource, TDest> expression)
        {
            expression.ForAllMembers(opt => opt.Ignore());
            return expression;
        }

Будьте осторожны, он будет игнорировать все, и если вы не добавите пользовательские сопоставления, они уже игнорируются и не будут работать

Кроме того, я хочу сказать, если у вас есть модульный тест для AutoMapper. И вы проверяете, что все модели со всеми свойствами отображаются правильно, вы не должны использовать такой метод расширения

вы должны написать игнорировать явно

Обычно вам это не понадобится. У меня была временная конфигурация, и я использовал ее:

       cfg.ShouldMapProperty = p => false;

Тогда все явные сопоставления работали нормально. Автокартограф 12+

Если мы хотим сопоставить только свойства исходного объекта, мы можем использовать следующую перегрузку метода карты:

      DestSampleObjetc dest = new();    
dest = mapper.Map(source, dest);

Я сделал небольшое улучшение. Вот код:

      public static IMappingExpression<TSource, TDestination> IgnoreAllNonExisting<TSource, TDestination>

        (this IMappingExpression<TSource, TDestination> expression)

    {

        var flags = BindingFlags.Public | BindingFlags.Instance;

        var sourceType = typeof(TSource);

        var destinationProperties = typeof(TDestination).GetProperties(flags);



        var memberConfigurations = expression.GetType()

            .GetProperty("MemberConfigurations",

                BindingFlags.NonPublic | BindingFlags.Instance)

            ?.GetValue(expression) as List<IPropertyMapConfiguration>;

        var mappedProperties = memberConfigurations?

            .Select(p => p.DestinationMember.Name)

            .ToList();

        foreach (var property in destinationProperties)

        {

           

            if (mappedProperties != null && mappedProperties.Contains(property.Name))

                continue;

            expression.ForMember(property.Name, opt => opt.Ignore());

        }

        return expression;

    }

В версии 3.3.1 вы можете просто использовать IgnoreAllPropertiesWithAnInaccessibleSetter() или же IgnoreAllSourcePropertiesWithAnInaccessibleSetter() методы.

Текущее (версия 9) решение для игнорирования свойств, которые не существуют в целевом типе, заключается в создании перевернутого отображения и его реверсировании:

var config = new MapperConfiguration(cfg => {
  cfg.CreateMap<TheActualDestinationType, TheActualSourceType>().ReverseMap();
});

Версия 12 Крайне плохой код, но он решил срочную работу.

            CreateMap<RepairPutRequest, Repair>(MemberList.None) 
                .ForAbdusselamMember(x => x.ClosedLostTimeId, y => y.MapFrom(z => z.ClosedLostTimeId))
                .ForAbdusselamMember(x => x.Explanation, y => y.MapFrom(z => z.Explanation)).IgnoreUnmapped()
                ; 
        public static class MappingExtensions
    {
        public static Dictionary<string, List<string>> list = new Dictionary<string, List<string>>();
        public static IMappingExpression<TSource, TDestination> IgnoreUnmapped<TSource, TDestination>(this IMappingExpression<TSource, TDestination> expression)
        {
             var sourceType = typeof(TSource);
            var destinationType = typeof(TDestination);
            var key =$"{sourceType.FullName}__||__{destinationType.FullName}"   ;
            var t = list[key];
            foreach (var item in destinationType.GetProperties())
            {
                if (!t.Contains(item.Name)) {
                    expression.ForMember(item.Name, x => x.Ignore());
                    }
            }
            return expression;
        }
        public static IMappingExpression<TSource, TDestination> ForAbdusselamMember<TSource, TDestination, TMember>(this IMappingExpression<TSource, TDestination> expression,
            Expression<Func<TDestination, TMember>> destinationMember, Action<IMemberConfigurationExpression<TSource, TDestination, TMember>> memberOptions )
        {
            var sourceType = typeof(TSource);
            var destinationType = typeof(TDestination);
            var key = $"{sourceType.FullName}__||__{destinationType.FullName}";
            if (!list.ContainsKey(key))
            {
                list[key]=new List<string>();
            }
            expression.ForMember(destinationMember, memberOptions);
            var memberInfo = ReflectionHelper.FindProperty(destinationMember);

            list[key].Add(memberInfo.Name);
            return expression;
        }
          
    }
Другие вопросы по тегам