AutoMapper: "Ignorar o resto"?

206

Existe uma maneira de dizer ao AutoMapper para ignorar todas as propriedades, exceto aquelas mapeadas explicitamente?

Eu tenho classes DTO externas que provavelmente mudam do lado de fora e quero evitar especificar que cada propriedade seja ignorada explicitamente, pois a adição de novas propriedades interromperá a funcionalidade (causará exceções) ao tentar mapeá-las em meus próprios objetos.

Igor Brejc
fonte
1
com o ValueInjecter valueinjecter.codeplex.com/documentation, você cria ValueInjections que têm o algoritmo de mapeamento e o mapeamento entre propriedades específicas e não se importam com o restante das propriedades
Omu
24
Para aqueles que usam o Automapper> versão 5, pule para baixo para ver as respostas detalhadas.ForAllOtherMembers(opts => opts.Ignore())
Jack Ukleja 21/10
@Schneider ".ForAllOtherMembers (opts => opts.Ignore ())" é diferente com a extensão "IgnoreAllNonExisting" aqui, a principal diferença é se você não configurou a propriedade explicitamente com ".ForAllOtherMembers (opts => opts.Ignore ( )) "você não receberá nada mapeado. use "IgnoreAllNonExisting" sem a propriedade de configuração explicitamente, você ainda terá algumas propriedades mapeadas (propriedades com o mesmo nome) com valor.
Dragon
Sim. O ForAllOtherMembers é a resposta. As respostas IgnoreUnmapped não fazem nada, exceto fazer com que o valid-config-assert seja aprovado, porque os membros não mapeados são ignorados de qualquer maneira.
N17k
É importante notar que, ao fazer isso, você esconde explicitamente alterações potencialmente relevantes ou importantes nas classes que estão sendo mapeadas. Ter mapeamentos explícitos para cada propriedade deixará você com um teste quebrado sempre que a classe mapeada for alterada, forçando-o a avaliar adequadamente. (Como você tem um teste para fazer a AssertConfigurationIsValid()ligação) Por causa disso, considero "Ignorar o resto" um antipadrão.
Arve Systad 30/09/19

Respostas:

83

Este é um método de extensão que escrevi que ignora todas as propriedades não existentes no destino. Não tenho certeza se ainda será útil, pois a pergunta tem mais de dois anos, mas encontrei o mesmo problema ao adicionar muitas chamadas manuais de Ignorar.

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;
}

Uso:

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

ATUALIZAÇÃO : Aparentemente, isso não funciona corretamente se você tiver mapeamentos personalizados porque os substitui. Eu acho que ainda poderia funcionar se chamar IgnoreAllNonExisting primeiro e depois os mapeamentos personalizados posteriormente.

O schdr tem uma solução (como resposta a esta pergunta) que é usada Mapper.GetAllTypeMaps()para descobrir quais propriedades não estão mapeadas e ignorá-las automaticamente. Parece uma solução mais robusta para mim.

Can Gencer
fonte
Não uso o AutoMapper há algum tempo, mas aceitarei sua resposta, se funcionar para você :).
Igor Brejc
2
Obrigado!! Achei isso muito útil. Ignorar propriedades individualmente estava anulando o propósito de usar o automapper na minha situação.
Daniel Robinson
Veja a resposta seguinte para uma que não tenha o problema de sobreposição
Jason Coyne
3
Este método deve estar no código nativo do autoMapper! Muito bom, obrigado!
Felipe Oriani
2
FYI, o próprio (escritor de AutoMapper) Jimmy comentou abaixo resposta que @ do nazim está correta para a versão 5+
Worthy7
244

Pelo que entendi, a pergunta era que existem campos no destino que não possuem um campo mapeado na origem, e é por isso que você está procurando maneiras de ignorar esses campos de destino não mapeados.

Em vez de implementar e usar esse método de extensão, você pode simplesmente usar

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

Agora, o automapper sabe que precisa validar apenas que todos os campos de origem estão mapeados, mas não o contrário.

Você também pode usar:

Mapper.CreateMap<sourceModel, destinationModel>(MemberList.Destination);  
Nazim Hafeez
fonte
10
Essa resposta deve ter mais votos positivos, talvez até marcada como a resposta. Ele resolveu o meu problema e, da mesma forma MemberList.Destination, resolveria o problema das operações.
precisa saber é o seguinte
1
Ele não vai funcionar se você deseja ignorar algumas propriedades em ambos origem e destino :)
RealWillyWoka
62
Para quem chega mais tarde, ESTA É A RESPOSTA CORRETA PARA 5.0
Jimmy Bogard
3
parece bacana, mas não funcionou para mim .. eu tentei origem e de destino, mas ele continua reclamando sobre o mesmo objeto propriedade faltando um mapa
o Sonic Alma
1
Usando 6.0.2 e isso não funciona período. Qualquer propriedade que não seja mapeada do destino para a origem, substitui as propriedades na origem por nulos e 0s. Além disso, o código não deixa claro o que você está fazendo, principalmente se estiver trabalhando em equipe. É por isso que eu fortemente não gostam deste código, e por isso eu prefiro palavras escolhidas como a resposta sugerida "IgnoreAllNonExisting"
sksallaj
222

Atualizei a extensão de Can Gencer para não substituir nenhum mapa existente.

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;
}

Uso:

Mapper.CreateMap<SourceType, DestinationType>()
                .ForMember(prop => x.Property, opt => opt.MapFrom(src => src.OtherProperty))
                .IgnoreAllNonExisting();
Robert Schroeder
fonte
4
+1, Obrigado por postar esta solução. Levei horas para descobrir erros estranhos quando uso a solução em goo.gl/rG7SL , até que eu tropecei neste post novamente.
Nordin
3
Eu recomendo o método de Yohanb abaixo sobre isso. Existem alguns casos de canto em que isso não funciona, pois aparece.
Jon Barker
3
Isso pode ser feito no AutoMapper 4.2? (A Mapper.GetAllTypeMaps()é preterido)
mrmashal
14
Para a versão AutoMapper 5+, substitua Mapper.GetAllTypeMaps()por Mapper.Configuration.GetAllTypeMaps(). Aqui é a referência github.com/AutoMapper/AutoMapper/issues/1252
Sergey G.
5
Para novas pessoas lendo isso. Esta resposta é para o AutoMapper 2 e, no momento em que escrevemos este comentário, estamos na versão 6. Essa é uma maneira mais simples e mais limpa de usar a enumeração MemberList. Veja a edição 1839 do Github e uma solução melhor. github.com/AutoMapper/AutoMapper/issues/1839 Então exemplo: stackoverflow.com/a/31182390/3850405
Ogglas
83

Consegui fazer isso da seguinte maneira:

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*/);
...

Nota: estou usando o AutoMapper v.2.0.

Yohanb
fonte
4
muito obrigado! Ele funciona como um encanto. i tentou primeiro a cadeia de chamadas, mas ForAllMembers apenas voltar :( vazio Não era óbvio que um IgnoreAll anterior podem ser modificados posteriormente..
SeriousM
5
Também não gosto dessa maneira ... se você tem 50 membros e deseja ignorar 25 ... então qual é o sentido do ajuste automático se você ainda precisa ignorar 25 membros. Se os nomes corresponderem e houver propriedades que não correspondam .. por que não deixar claro para dizer ao automapper para não corresponder às propriedades não mapeadas e passando toda a digitação?
Sksallaj 13/0918
71

A versão 5.0.0-beta-1 do AutoMapper apresenta o ForAllOtherMembersmétodo de extensão para que você possa fazer isso agora:

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());

Esteja ciente de que existe uma vantagem em mapear explicitamente cada propriedade, pois você nunca terá problemas em mapear falhas silenciosas que surgem quando você esquece de mapear uma propriedade.

Talvez, no seu caso, talvez seja sensato ignorar todos os outros membros, adicionar a TODOpara voltar e torná-los explícitos após o término da frequência das alterações nessa classe.

ajbeaven
fonte
3
Surpreendente, isso levou até a versão 5. Vejam quantas votações positivas e tentaram respostas para essa pergunta ... alguma coisa errada com a governança da Automapper, eu me pergunto?
Jack Ukleja
Obrigado por isso, demorei um pouco para rolar para baixo, mas isso, mas funciona perfeitamente.
Cobolstinks
2
Você pode até colocar a linha ForAllOtherMembers primeiro e tudo funcionará da mesma maneira, o que é bom se você tiver algum tipo de configuração de classe base.
N73k
Agora é a abordagem preferida. Gostaria de saber se o OP poderia alterar a resposta aceita?
usar o seguinte comando
1
Existe um equivalente para ignorar as propriedades no objeto de origem? Algo como ForAllOtherSourceMembers?
precisa saber é o seguinte
44

No AutoMapper 5.0, a .TypeMappropriedade on IMappingExpressionfoi desativada, o que significa que a solução 4.2 não funciona mais. Eu criei uma solução que usa a funcionalidade original, mas com uma sintaxe diferente:

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>();
}

Implementação:

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));
    }
}
Richard
fonte
3
Como você usaria isso em uma CreateMap<TSource,TDest>()expressão encadeada em a Profile?
jmoerdyk
2
Obrigado por isso. O método GetUnmappedPropertyNames retorna todos os nomes de propriedades não mapeados, tanto na origem quanto no destino, o que parece estar quebrado em um mapa reverso, então tive que fazer uma pequena alteração em IgnoreUnmapped para verificar se a propriedade não mapeada estava na origem ou destino e ignorar adequadamente. Aqui está um violino demonstrando o problema e a atualização: dotnetfiddle.net/vkRGJv
Mun
1
Atualizei minha resposta para incluir suas descobertas - não uso mapeamentos de código-fonte, por isso não encontrei isso! Obrigado.
Richard
1
Isso não funciona no PCL sem reflexão disponível, GetProperty (propName) não existe.
George Taskos
Não vejo como isso é uma solução para a questão ou como isso faz alguma coisa. As propriedades não mapeadas já serão ignoradas - porque não estão mapeadas . O cartaz dizia "como você ignora adereços, a menos que eles sejam explicitamente mapeados". Isso significa que, se eu tiver Src.MyProp e Dest.MyProp, esse mapeamento deverá ser ignorado, a menos que haja uma chamada explícita para MapFrom & ForMember for MyProp. Portanto, o mapeamento padrão precisa ser ignorado. A única coisa que essa solução faz é fazer com que a coisa config-valid-assert passe - o que você não precisa, de qualquer maneira, para o mapeamento funcionar.
N73k
17

Há alguns anos desde que a pergunta foi feita, mas esse método de extensão me parece mais limpo, usando a versão atual do 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;
}
Iravanchi
fonte
16

Para aqueles que estão usando a API não estática na versão 4.2.0 e acima, o seguinte método de extensão (encontrado aqui na AutoMapperExtensionsclasse) pode ser usado:

// from http://stackoverflow.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;
}

O importante aqui é que, uma vez removida a API estática, o código que Mapper.FindTypeMapFornão funcionará mais, daí o uso do expression.TypeMapcampo.

nick_w
fonte
7
A partir do 5.0, expression.TypeMapnão está mais disponível. Aqui está a minha solução para o 5.0
Richard
Eu tive que usar public static IMappingExpression<TSource, TDestination> IgnoreAllNonExisting<TSource, TDestination>(this IMappingExpression<TSource, TDestination> expression)para corrigir problemas de tipo.
Nick M
16

Para o Automapper 5.0, para pular todas as propriedades não mapeadas, basta colocar

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

no final do seu perfil.

Por exemplo:

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());
    }
}

Nesse caso, apenas o campo Id para o objeto de saída será resolvido e todos os outros serão ignorados. Funciona como um encanto, parece que não precisamos mais de extensões complicadas!

transferência de quadro
fonte
10

Atualizei a resposta de Robert Schroeder para o AutoMapper 4.2. Com configurações de mapeador não estático, não podemos usar Mapper.GetAllTypeMaps(), mas ele expressiontem uma referência ao necessário 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;
}
Mrmashal
fonte
Não funciona no AutoMapper 5.0. A propriedade .TypeMap em IMappingExpression não está disponível. Para 5. + versão ver extensões na resposta de Richard
Michael Freidgeim
Funciona com o AM 4.2
Leszek P
8

Como você prefere especificar que certos membros sejam ignorados? Existe uma convenção, classe base ou atributo que você gostaria de aplicar? Depois que você começa a especificar explicitamente todos os mapeamentos, não tenho certeza de qual valor você obteria do AutoMapper.

Jimmy Bogard
fonte
Jimmy, você tem razão sobre explicitação. Quanto à maneira de conseguir isso da maneira mais elegante: classes e atributos base não funcionariam nessa situação, pois as classes de destino não estão realmente sob meu controle - elas são geradas automaticamente a partir do contrato de dados XSD, portanto, seria necessário para editar manualmente esse código após cada ciclo de geração. Eu acho que a solução depende de um caso concreto. Talvez uma interface fluente semelhante à que o Castelo de Windsor fornece para selecionar quais componentes registrar no contêiner poderia ser uma solução?
Igor Brejc
Ah, isso faz mais sentido agora. Essa é uma característica interessante, vou ver essa no período 2.1.
precisa
2
Que tal apenas ter um valor configurável em que você possa "ignorar" todos os campos não existentes.
Ricardo Sanchez
6
Esta não é uma resposta para a pergunta.
user2864740
Oi Jimmy, você é o autor, correto? Eu gostaria de poder ignorar todas as propriedades inexistentes, sendo um comportamento padrão (pode ser controlado por um sinalizador). Além disso, estou tendo um erro estranho no AutoMapper que não consigo descobrir. Não me fornece nenhum detalhe.
Naomi
7

Parece uma pergunta antiga, mas pensei em postar minha resposta para qualquer pessoa que parecesse.

Eu uso ConstructUsing, inicializador de objeto juntamente com ForAllMembers ignorar, por exemplo

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

A única informação sobre como ignorar muitos membros é este tópico - http://groups.google.com/group/automapper-users/browse_thread/thread/9928ce9f2ffa641f . Eu acho que você pode usar o truque usado em ProvidingCommonBaseClassConfiguration para ignorar propriedades comuns para classes semelhantes.
E não há informações sobre a funcionalidade "Ignorar o resto". Eu olhei o código antes e me parece que será muito e muito difícil adicionar essa funcionalidade. Além disso, você pode tentar usar algum atributo e marcar propriedades ignoradas e adicionar algum código genérico / comum para ignorar todas as propriedades marcadas.

zihotki
fonte
1
Talvez uma maneira seria usar o método ForAllMembers e implementar o meu próprio IMemberConfigurationExpression, que recebe uma string contendo os nomes das propriedades que não devem ser ignoradas e, em seguida, percorrer o restante deles e chamar Ignore (). Apenas uma ideia, não tenho certeza se funcionaria.
Igor Brejc
Sim, isso também pode funcionar, mas esse método é mais complicado do que usar atributos, mas oferece mais flexibilidade. É uma pena que não há bala de prata :(.
zihotki
1

Eu sei que essa é uma pergunta antiga, mas @jmoerdyk na sua pergunta:

Como você usaria isso em uma expressão CreateMap () encadeada em um perfil?

você pode usar esta resposta como essa dentro do ctor Profile

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

Você pode usar ForAllMembers, em vez de sobrescrever apenas o necessário

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

Cuidado, ele ignorará tudo e, se você não adicionar um mapeamento personalizado, eles já serão ignorados e não funcionarão.

Além disso, quero dizer, se você tiver teste de unidade para o AutoMapper. E você testa que todos os modelos com todas as propriedades mapeadas corretamente não devem usar esse método de extensão

você deve escrever ignorar explicitamente

Anatoli Klamer
fonte
-1

A solução atual (versão 9) para ignorar as propriedades que não existem no tipo de destino é criar um mapeamento invertido e revertê-lo:

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

Na versão 3.3.1, você simplesmente pode usar IgnoreAllPropertiesWithAnInaccessibleSetter()ou IgnoreAllSourcePropertiesWithAnInaccessibleSetter()métodos.

Ivan Kochurkin
fonte
6
Isso não funciona de acordo com a pergunta do pôster original. Esses métodos ignoram apenas propriedades protegidas ou privadas, não propriedades ausentes da origem, mas presentes no tipo de destino.
Dan