AutoMapper: "¿Ignorar el resto"?


206

¿Hay alguna manera de decirle a AutoMapper que ignore todas las propiedades, excepto las que se asignan explícitamente?

Tengo clases externas de DTO que es probable que cambien desde el exterior y quiero evitar especificar que cada propiedad se ignore explícitamente, ya que agregar nuevas propiedades romperá la funcionalidad (causará excepciones) cuando intente asignarlas a mis propios objetos.


1
con ValueInjecter valueinjecter.codeplex.com/documentation usted crea ValueInjections que tienen su algoritmo de mapeo y un mapa entre propiedades específicas, y no les importa el resto de las propiedades
Omu

24
Para aquellos que usan Automapper> versión 5, salte hacia abajo para ver las respuestas que detallan.ForAllOtherMembers(opts => opts.Ignore())
Jack Ukleja

@Schneider ".ForAllOtherMembers (opts => opts.Ignore ())" es diferente con la extensión "IgnoreAllNonExisting" aquí, la diferencia principal es que si no configuró la propiedad explícitamente, con ".ForAllOtherMembers (opts => opts.Ignore ( )) "no obtendrá nada asignado. use "IgnoreAllNonExisting" sin la propiedad de configuración explícitamente, aún obtendrá algunas propiedades asignadas (propiedades con el mismo nombre) con valor.
Dragón

Si. ForAllOtherMembers es la respuesta. Las respuestas IgnoreUnmapped no hacen nada excepto causar que se pase la config-valid-afirmar, porque los miembros no mapeados se ignoran de todos modos.
N73k

Vale la pena señalar que al hacer esto, oculta explícitamente los cambios potencialmente relevantes o importantes en las clases que se asignan. Tener asignaciones explícitas para cada propiedad lo dejará con una prueba interrumpida cada vez que cambie la clase asignada, lo que le obligará a evaluarla correctamente. (Dado que tiene una prueba haciendo la AssertConfigurationIsValid()llamada) Debido a esto, considero que "Ignorar el resto" es un antipatrón.
Arve Systad

Respuestas:


83

Este es un método de extensión que escribí que ignora todas las propiedades no existentes en el destino. No estoy seguro de si seguirá siendo útil ya que la pregunta tiene más de dos años, pero me encontré con el mismo problema al tener que agregar muchas llamadas de Ignorar manuales.

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

ACTUALIZACIÓN : Aparentemente, esto no funciona correctamente si tiene asignaciones personalizadas porque las sobrescribe. Supongo que aún podría funcionar si llama a IgnoreAllNonExisting primero y luego las asignaciones personalizadas más tarde.

schdr tiene una solución (como respuesta a esta pregunta) que utiliza Mapper.GetAllTypeMaps()para averiguar qué propiedades no están asignadas y las ignora automáticamente. Parece una solución más robusta para mí.


No he usado AutoMapper por algún tiempo, pero aceptaré tu respuesta si te funciona :).
Igor Brejc

2
¡¡Gracias!! Esto me pareció muy útil. Ignorar las propiedades individualmente estaba frustrando el propósito de usar automapper en mi situación.
Daniel Robinson

Vea la siguiente respuesta para una que no tenga el problema de sobrescritura
Jason Coyne el

3
¡Este método debe estar en el código nativo de autoMapper! ¡Muy bonito, gracias!
Felipe Oriani

2
Para su información, Jimmy mismo (escritor de AutoMapper) ha comentado a continuación que la respuesta de @ nazim es correcta para la versión 5+
Worthy7

244

Por lo que entendí, la pregunta era que hay campos en el destino que no tienen un campo mapeado en la fuente, por lo que está buscando formas de ignorar esos campos de destino no mapeados.

En lugar de implementar y usar estos métodos de extensión, simplemente podría usar

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

Ahora el automapper sabe que solo necesita validar que todos los campos de origen estén asignados, pero no al revés.

También puedes usar:

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

10
Esta respuesta debería tener más votos a favor, tal vez incluso esté marcada como la respuesta. Resolvió mi problema y de manera similar MemberList.Destinationresolvería el problema de operaciones.
Tedd Hansen el

1
No funcionará si desea ignorar algunas propiedades tanto en origen como en destino :)
RealWillyWoka

62
Para cualquiera que venga después, ESTA ES LA RESPUESTA CORRECTA PARA 5.0
Jimmy Bogard

3
se ve ingenioso pero no funcionó para mí ... probé Source y Destination, pero sigue quejándose de que el mismo objeto de propiedad no tiene un mapa
Sonic Soul

1
Usando 6.0.2 y esto no funciona período. Cualquier propiedad que no esté asignada del destino al origen, sobrescribe las propiedades en el origen con nulos y 0. Además, el código no deja claro lo que está haciendo, especialmente si está trabajando en un equipo. Es por eso que no me gusta mucho este código, y por eso prefiero palabras selectas como la respuesta sugerida "IgnoreAllNonExisting"
sksallaj

222

He actualizado la extensión de Can Gencer para no sobrescribir ningún 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();

44
+1, gracias por publicar esta solución. Me llevó horas descubrir errores raros cuando uso la solución en goo.gl/rG7SL , hasta que vuelvo a esta publicación.
Nordin

3
Recomiendo el método de Yohanb a continuación sobre esto. Hay algunos casos de esquina en los que esto no funciona porque parece.
Jon Barker

3
¿Se puede hacer esto en AutoMapper 4.2? (El Mapper.GetAllTypeMaps()está en desuso)
mrmashal

14
Para la versión AutoMapper 5+, simplemente reemplace Mapper.GetAllTypeMaps()con Mapper.Configuration.GetAllTypeMaps(). Aquí está la referencia github.com/AutoMapper/AutoMapper/issues/1252
Sergey G.

55
Para gente nueva que lee esto. Esta respuesta es para AutoMapper 2 y en el momento de escribir este comentario estamos en la versión 6. Este es un truco y una forma mucho más limpia es usar la enumeración de MemberList. Vea el número 1839 de Github y una mejor solución. github.com/AutoMapper/AutoMapper/issues/1839 Ejemplo: stackoverflow.com/a/31182390/3850405
Ogglas

83

He podido hacer esto de la siguiente manera:

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: Estoy usando AutoMapper v.2.0.


44
¡Muchas gracias! funciona a las mil maravillas. Primero intenté encadenar las llamadas, pero ForAllMembers solo devuelve void :(. No era obvio que un IgnoreAll anterior se pueda modificar más tarde.
SeriousM

55
Tampoco me gusta de esta manera ... si tienes 50 miembros, y quieres ignorar a 25 ... entonces, ¿cuál es el punto de automapper si todavía tienes que ignorar a 25 miembros? Si los nombres coinciden, y hay propiedades que no coinciden ... ¿por qué no dejar en claro decirle a automapper que no coincida en propiedades no asignadas y pasando todo el tipeo?
sksallaj

71

La versión 5.0.0-beta-1 de AutoMapper presenta el ForAllOtherMembersmétodo de extensión para que ahora pueda hacer esto:

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

Tenga en cuenta que existe una ventaja al mapear explícitamente cada propiedad, ya que nunca tendrá problemas de mapeo silencioso que surgen cuando se olvida de mapear una propiedad.

Quizás en su caso, sería aconsejable ignorar a todos los demás miembros y agregar un TODOpara volver y hacerlos explícitos después de que se establezca la frecuencia de los cambios en esta clase.


3
Sorprendente, esto tomó hasta la versión 5. Mira cuántos votos positivos e intentos de respuesta a esta pregunta ... me pregunto algo malo con el gobierno de Automapper.
Jack Ukleja

Gracias por esto, me tomó un tiempo desplazarme hacia abajo, pero esto, pero funciona perfectamente.
cobolstinks

2
Incluso puede poner la línea ForAllOtherMembers primero y las cosas funcionarán igual, lo cual es bueno si tiene algún tipo de configuración de clase base.
N73k

Este es ahora el enfoque preferido. ¿Se pregunta si el OP podría cambiar la respuesta aceptada?
Chase Florell

1
¿Hay un equivalente para ignorar las propiedades en el objeto de origen? Algo como ForAllOtherSourceMembers?
SuperJMN

44

A partir de AutoMapper 5.0, la .TypeMappropiedad IMappingExpressionha desaparecido, lo que significa que la solución 4.2 ya no funciona. Creé una solución que usa la funcionalidad original pero con una sintaxis 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>();
}

Implementación:

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

3
¿Cómo usarías esto en una CreateMap<TSource,TDest>()expresión encadenada en a Profile?
jmoerdyk

2
Gracias por esto. El método GetUnmappedPropertyNames devuelve todos los nombres de propiedades sin asignar, tanto en el origen como en el destino, que parece estar roto en un mapa inverso, por lo que tuve que hacer un pequeño cambio en IgnoreUnmapped para verificar si la propiedad no asignada estaba en el origen o el destino e ignorar en consecuencia. Aquí hay un violín que demuestra el problema y la actualización: dotnetfiddle.net/vkRGJv
Mun

1
He actualizado mi respuesta para incluir sus hallazgos: no uso las asignaciones de origen, ¡así que no me he encontrado con esto! Gracias.
Richard

1
Esto no funciona en PCL sin reflexión disponible, GetProperty (propName) no existe.
George Taskos

No veo cómo esto es una solución a la pregunta, o cómo esto incluso hace algo. Las propiedades no asignadas ya serán ignoradas, porque no están asignadas . El póster decía "¿cómo ignoras los accesorios a menos que estén explícitamente mapeados". Eso significa que si tengo Src.MyProp y Dest.MyProp, esa asignación debe ignorarse a menos que haya una llamada explícita a MapFrom & ForMember para MyProp. Por lo tanto, la asignación predeterminada debe ignorarse. Lo único que hace esta solución es hacer que pase la cosa config-valida-afirmación, que de todos modos no necesita para que funcione la asignación.
N73k

17

Han pasado algunos años desde que se hizo la pregunta, pero este método de extensión me parece más limpio, usando la versión actual de 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;
}

16

Para aquellos que usan la API no estática en la versión 4.2.0 y superior, se puede usar el siguiente método de extensión (que se encuentra aquí en la AutoMapperExtensionsclase):

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

Lo importante aquí es que una vez que se elimina la API estática, el código como Mapper.FindTypeMapForya no funcionará, de ahí el uso del expression.TypeMapcampo.


77
A partir de 5.0, expression.TypeMapya no está disponible. Aquí está mi solución para 5.0
Richard

Tuve que usar public static IMappingExpression<TSource, TDestination> IgnoreAllNonExisting<TSource, TDestination>(this IMappingExpression<TSource, TDestination> expression)para solucionar problemas de tipo.
Nick M

16

Para Automapper 5.0 para omitir todas las propiedades no asignadas, solo necesita poner

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

al final de tu perfil.

Por ejemplo:

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

En este caso, solo el campo Id para el objeto de salida se resolverá, todos los demás se omitirán. Funciona de maravilla, ¡parece que ya no necesitamos extensiones difíciles!


10

He actualizado la respuesta de Robert Schroeder para AutoMapper 4.2. Con configuraciones de mapeador no estático, no podemos usar Mapper.GetAllTypeMaps(), pero expressiontiene una referencia a lo requerido 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;
}

No funciona en AutoMapper 5.0. La propiedad .TypeMap en IMappingExpression no está disponible. Para la versión 5. + ver extensiones en la respuesta de Richard
Michael Freidgeim

Funciona con AM 4.2
Leszek P

8

¿Cómo preferiría especificar que ciertos miembros sean ignorados? ¿Hay alguna convención, clase base o atributo que le gustaría aplicar? Una vez que entre en el negocio de especificar todas las asignaciones explícitamente, no estoy seguro de qué valor obtendría de AutoMapper.


Jimmy, tienes un punto sobre lo explícito. En cuanto a la forma de lograr esto de la manera más elegante: las clases base y los atributos no funcionarían en esta situación, ya que las clases objetivo no están realmente bajo mi control: se generan automáticamente a partir del contrato de datos XSD, por lo que uno tendría editar manualmente este código después de cada ciclo de generación. Supongo que la solución depende de un caso concreto. ¿Quizás una interfaz fluida similar a la que proporciona Windsor Castle para seleccionar qué componentes registrar en el contenedor podría ser una solución?
Igor Brejc 05 de

Ah, eso tiene más sentido ahora. Esa es una característica interesante, la veré en el marco de tiempo 2.1.
Jimmy Bogard el

2
¿Qué tal tener un valor configurable en el que pueda "ignorar" todos los campos no existentes?
Ricardo Sánchez

66
Esta no es una respuesta a la pregunta.
usuario2864740

Hola Jimmy, eres el autor, ¿correcto? Me gustaría poder ignorar que todas las propiedades no existentes son un comportamiento predeterminado (puede ser controlado por una bandera). Además, tengo un extraño error de AutoMapper que no puedo resolver. No me da ningún detalle.
Naomi

7

Esta parece una vieja pregunta, pero pensé que publicaría mi respuesta para cualquier otra persona que parezca que era.

Utilizo ConstructUsing, el inicializador de objeto junto con ForAllMembers ignora, por ejemplo

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

1

La única información sobre ignorar a muchos miembros es este hilo: http://groups.google.com/group/automapper-users/browse_thread/thread/9928ce9f2ffa641f . Creo que puede usar el truco utilizado en Pro ProvideCommonBaseClassConfiguration para ignorar las propiedades comunes de clases similares.
Y no hay información sobre la funcionalidad "Ignorar el resto". He visto el código antes y me parece que será muy y muy difícil agregar dicha funcionalidad. También puede intentar usar algún atributo y marcar con él las propiedades ignoradas y agregar algún código genérico / común para ignorar todas las propiedades marcadas.


1
Quizás una forma sería usar el método ForAllMembers e implementar mi propia IMemberConfigurationExpression que recibe una cadena que contiene los nombres de las propiedades que no deben ignorarse, y luego pasar por el resto de ellas y llamar a Ignore (). Solo una idea, no estoy seguro de si funcionaría.
Igor Brejc 05 de

Sí, esto también puede funcionar, pero este método es más complicado que usar atributos, pero ofrece más flexibilidad. Es una pena que no haya bala de plata :(.
zihotki 05 de

1

Sé que esta es una vieja pregunta, pero @jmoerdyk en tu pregunta:

¿Cómo usarías esto en una expresión encadenada de CreateMap () en un perfil?

puedes usar esta respuesta como esta dentro del Perfil ctor

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

0

Puede usar ForAllMembers, que sobrescribir solo se necesita de esta manera

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

Tenga cuidado, lo ignorará todo, y si no agrega mapeo personalizado, ya se ignorarán y no funcionarán.

También, quiero decir, si tiene prueba de unidad para AutoMapper. Y prueba que todos los modelos con todas las propiedades asignadas correctamente no deberían usar dicho método de extensión

deberías escribir ignorar explícitamente


-1

La solución actual (versión 9) para ignorar las propiedades que no existen en el tipo de destino es crear una asignación invertida y revertirla:

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

-2

En la versión 3.3.1 simplemente puede usar IgnoreAllPropertiesWithAnInaccessibleSetter()o IgnoreAllSourcePropertiesWithAnInaccessibleSetter()métodos.


66
Esto no funciona según la pregunta del póster original. Estos métodos solo ignoran las propiedades protegidas o privadas, no las propiedades que faltan en el origen pero están presentes en el tipo de destino.
Dan
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.