AutoMapper: "Ignorer le reste"?

206

Existe-t-il un moyen de dire à AutoMapper d'ignorer toutes les propriétés à l'exception de celles qui sont mappées explicitement?

J'ai des classes DTO externes qui sont susceptibles de changer de l'extérieur et je veux éviter de spécifier explicitement chaque propriété à ignorer, car l'ajout de nouvelles propriétés cassera la fonctionnalité (provoquera des exceptions) lors de la tentative de les mapper dans mes propres objets.

Igor Brejc
la source
1
avec ValueInjecter valueinjecter.codeplex.com/documentation vous créez des ValueInjections qui ont leur alghorithme de mappage et mappent entre des propriétés spécifiques, et ils ne se soucient pas du reste des propriétés
Omu
24
Pour ceux qui utilisent Automapper> version 5, sautez vers le bas pour voir les réponses détaillées.ForAllOtherMembers(opts => opts.Ignore())
Jack Ukleja
@Schneider ".ForAllOtherMembers (opts => opts.Ignore ())" est différent avec l'extension "IgnoreAllNonExisting" ici, la principale différence est que si vous n'avez pas configuré la propriété explicitement, avec ".ForAllOtherMembers (opts => opts.Ignore (( )) "vous n'aurez rien mappé. utilisez "IgnoreAllNonExisting" sans la propriété de configuration de manière explicite, vous obtenez toujours une propriété mappée (propriétés du même nom) avec une valeur.
Dragon
Oui. Le ForAllOtherMembers est la réponse. Les réponses IgnoreUnmapped ne font rien sauf faire passer la config-valid-assert, car les membres non mappés sont de toute façon ignorés.
N73k
Il convient de noter qu'en faisant cela, vous masquez explicitement les modifications potentiellement pertinentes ou importantes dans les classes en cours de mappage. Le fait d'avoir des mappages explicites pour chaque propriété vous laissera un test cassé chaque fois que la classe mappée changera, vous forçant à l'évaluer correctement. (Étant donné que vous avez un test pour faire l' AssertConfigurationIsValid()appel) Pour cette raison, je considère "Ignorer le reste" comme un contre-modèle.
Arve Systad

Réponses:

83

Il s'agit d'une méthode d'extension que j'ai écrite qui ignore toutes les propriétés non existantes sur la destination. Je ne sais pas si cela sera toujours utile car la question a plus de deux ans, mais j'ai rencontré le même problème en ajoutant beaucoup d'appels manuels Ignore.

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

Usage:

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

MISE À JOUR : Apparemment, cela ne fonctionne pas correctement si vous avez des mappages personnalisés car ils les écrasent. Je suppose que cela pourrait encore fonctionner si appelez d'abord IgnoreAllNonExisting puis les mappages personnalisés plus tard.

schdr a une solution (comme réponse à cette question) qui utilise Mapper.GetAllTypeMaps()pour découvrir quelles propriétés ne sont pas mappées et les ignorer automatiquement. Cela me semble être une solution plus robuste.

Can Gencer
la source
Je n'ai pas utilisé AutoMapper depuis un certain temps, mais j'accepterai votre réponse si cela fonctionne pour vous :).
Igor Brejc
2
Merci!! J'ai trouvé cela très pratique. Ignorer les propriétés individuellement allait à l'encontre de l'objectif d'utiliser automapper dans ma situation.
Daniel Robinson
Voir la réponse suivante pour celui qui n'a pas le problème d'écrasement
Jason Coyne
3
Cette méthode devrait être sur le code natif autoMapper! Très bien merci!
Felipe Oriani du
2
Pour info, Jimmy lui-même (écrivain d'AutoMapper) a commenté ci-dessous que la réponse de @ nazim est correcte pour la version 5+
Worthy7
244

D'après ce que j'ai compris, la question était qu'il y a des champs sur la destination qui n'ont pas de champ mappé dans la source, c'est pourquoi vous cherchez des moyens d'ignorer ces champs de destination non mappés.

Au lieu d'implémenter et d'utiliser ces méthodes d'extension, vous pouvez simplement utiliser

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

Maintenant, l'automappeur sait qu'il n'a qu'à valider que tous les champs source sont mappés mais pas l'inverse.

Vous pouvez aussi utiliser:

Mapper.CreateMap<sourceModel, destinationModel>(MemberList.Destination);  
Nazim Hafeez
la source
10
Cette réponse devrait avoir plus de votes positifs, peut-être même être marquée comme réponse. Cela a résolu mon problème et MemberList.Destinationrésoudrait de la même manière le problème des opérations.
Tedd Hansen du
1
Cela ne fonctionnera pas si vous souhaitez ignorer quelques propriétés sur la source et la destination :)
RealWillyWoka
62
À tous ceux qui viendront plus tard, CECI EST LA RÉPONSE CORRECTE POUR 5.0
Jimmy Bogard
3
semble astucieux mais n'a pas fonctionné pour moi .. j'ai essayé Source et Destination, mais il continue de se plaindre du même objet de propriété sans carte
Sonic Soul
1
Utiliser 6.0.2 et cela ne fonctionne pas. Toute propriété qui n'est pas mappée de la destination à la source, remplace les propriétés dans la source par des valeurs NULL et 0. De plus, le code ne précise pas ce que vous faites, surtout si vous travaillez en équipe. C'est pourquoi je n'aime pas beaucoup ce code, et pourquoi je préfère les mots de choix comme la réponse suggérée "IgnoreAllNonExisting"
sksallaj
222

J'ai mis à jour l'extension Can Gencer pour ne pas écraser les cartes existantes.

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

Usage:

Mapper.CreateMap<SourceType, DestinationType>()
                .ForMember(prop => x.Property, opt => opt.MapFrom(src => src.OtherProperty))
                .IgnoreAllNonExisting();
Robert Schroeder
la source
4
+1, Merci à vous d'avoir posté cette solution. Cela m'a pris des heures pour comprendre un bug bizarre lorsque j'utilise la solution dans goo.gl/rG7SL , jusqu'à ce que je tombe à nouveau sur ce post.
Nordin
3
Je recommande la méthode de Yohanb ci-dessous à ce sujet. Il y a des cas d'angle pour lesquels cela ne fonctionne pas car cela apparaît.
Jon Barker
3
Cela peut-il être fait dans AutoMapper 4.2? (Le Mapper.GetAllTypeMaps()est déconseillé)
mrmashal
14
Pour la version AutoMapper 5+, remplacez simplement Mapper.GetAllTypeMaps()par Mapper.Configuration.GetAllTypeMaps(). Voici la référence github.com/AutoMapper/AutoMapper/issues/1252
Sergey G.
5
Pour les nouvelles personnes qui lisent ceci. Cette réponse est pour AutoMapper 2 et au moment d'écrire ce commentaire, nous sommes à la version 6. C'est un hack et une manière beaucoup plus propre utilise l'énumération MemberList. Voir le numéro 1839 de Github et une meilleure solution. github.com/AutoMapper/AutoMapper/issues/1839 Donc exemple: stackoverflow.com/a/31182390/3850405
Ogglas
83

J'ai pu le faire de la manière suivante:

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

Remarque: j'utilise AutoMapper v.2.0.

Yohanb
la source
4
Merci beaucoup! il fonctionne comme un charme. j'ai essayé d'abord de chaîner les appels mais ForAllMembers vient de retourner vide :(. Il n'était pas évident qu'un précédent IgnoreAll puisse être modifié plus tard.
SeriousM
5
Je n'aime pas cette façon non plus .. si vous avez 50 membres et que vous voulez en ignorer 25 .. alors quel est l'intérêt de l'automappeur si vous devez toujours ignorer 25 membres. Si les noms correspondent et qu'il existe des propriétés qui ne correspondent pas .. pourquoi ne pas indiquer clairement à automapper de ne pas correspondre aux propriétés non mappées et en passant toutes les saisies?
sksallaj
71

La version 5.0.0-beta-1 d'AutoMapper introduit la ForAllOtherMembersméthode d'extension afin que vous puissiez maintenant le faire:

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

Sachez qu'il existe un avantage à mapper explicitement chaque propriété, car vous n'aurez jamais de problèmes de mappage échouant silencieusement qui surviennent lorsque vous oubliez de mapper une propriété.

Dans votre cas, il serait peut-être sage d'ignorer tous les autres membres et d'ajouter un TODOpour revenir et les rendre explicites une fois la fréquence des changements apportés à cette classe établie.

ajbeaven
la source
3
Incroyable, cela a pris jusqu'à la version 5. Regardez combien de votes positifs et de réponses à cette question ... quelque chose de mal avec la gouvernance d'Automapper, je me demande?
Jack Ukleja
Merci pour cela, m'a pris un certain temps pour y faire défiler mais cela, mais cela fonctionne parfaitement.
cobolstinks
2
Vous pouvez même mettre la ligne ForAllOtherMembers en premier et les choses fonctionneront de la même manière, ce qui est bien si vous avez une sorte de configuration de classe de base.
N73k
C'est maintenant l'approche privilégiée. Vous vous demandez si l'OP pourrait changer la réponse acceptée?
Chase Florell
1
Existe-t-il un équivalent pour ignorer les propriétés de l'objet source? Quelque chose comme ça ForAllOtherSourceMembers?
SuperJMN
44

Depuis AutoMapper 5.0, la .TypeMappropriété on a IMappingExpressiondisparu, ce qui signifie que la solution 4.2 ne fonctionne plus. J'ai créé une solution qui utilise la fonctionnalité d'origine mais avec une syntaxe différente:

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

La mise en oeuvre:

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
la source
3
Comment utiliseriez-vous cela dans une CreateMap<TSource,TDest>()expression enchaînée dans un Profile?
jmoerdyk
2
Merci pour cela. La méthode GetUnmappedPropertyNames renvoie tous les noms de propriété non mappés, à la fois sur la source et la destination, ce qui semble être rompu sur une carte inversée, j'ai donc dû apporter une petite modification à IgnoreUnmapped pour vérifier si la propriété non mappée était sur la source ou la destination et ignorer en conséquence. Voici un violon démontrant le problème et la mise à jour: dotnetfiddle.net/vkRGJv
Mun
1
J'ai mis à jour ma réponse pour inclure vos résultats - je n'utilise pas les mappages de source, donc je ne l'avais pas rencontré! Merci.
Richard
1
Cela ne fonctionne pas sur PCL sans réflexion disponible, GetProperty (propName) n'existe pas.
George Taskos du
Je ne vois pas comment cela est une solution à la question, ni comment cela fait quoi que ce soit. Propriétés inexplorées vont déjà être ignorés - parce qu'ils sont non cartographiées . L'affiche disait "comment ignorer les accessoires à moins qu'ils ne soient explicitement mappés". Cela signifie que si j'ai Src.MyProp et Dest.MyProp, ce mappage doit être ignoré sauf s'il y a eu un appel explicite à MapFrom & ForMember pour MyProp. Ainsi, le mappage par défaut doit être ignoré. La seule chose que cette solution fait est de faire passer la chose config-valid-assert - dont vous n'avez pas besoin de toute façon pour que le mappage fonctionne.
N73k
17

Il y a quelques années que la question a été posée, mais cette méthode d'extension me semble plus propre, en utilisant la version actuelle d'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
la source
16

Pour ceux qui utilisent l' API non statique dans les versions 4.2.0 et supérieures, la méthode d'extension suivante (trouvée ici dans la AutoMapperExtensionsclasse) peut être utilisée:

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

L'important ici est qu'une fois l'API statique supprimée, le code tel que Mapper.FindTypeMapForne fonctionnera plus, d'où l'utilisation du expression.TypeMapchamp.

nick_w
la source
7
À partir de la version 5.0, expression.TypeMapn'est plus disponible. Voici ma solution pour 5.0
Richard
J'ai dû utiliser public static IMappingExpression<TSource, TDestination> IgnoreAllNonExisting<TSource, TDestination>(this IMappingExpression<TSource, TDestination> expression)pour résoudre les problèmes de type.
Nick M
16

Pour Automapper 5.0 afin d'ignorer toutes les propriétés non mappées dont vous avez juste besoin

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

à la fin de votre profil.

Par exemple:

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

Dans ce cas, seul le champ Id pour l'objet de sortie sera résolu, tous les autres seront ignorés. Fonctionne comme un charme, il semble que nous n'ayons plus besoin d'extensions délicates!

framerelay
la source
10

J'ai mis à jour la réponse de Robert Schroeder pour AutoMapper 4.2. Avec les configurations de mappeur non statiques, nous ne pouvons pas utiliser Mapper.GetAllTypeMaps(), mais le expressionfait référence au requis 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
la source
Ne fonctionne pas dans AutoMapper 5.0. La propriété .TypeMap sur IMappingExpression n'est pas disponible. Pour la version 5. +, voir les extensions dans la réponse de Richard
Michael Freidgeim
Fonctionne avec AM 4.2
Leszek P
8

Comment préférez-vous spécifier que certains membres soient ignorés? Y a-t-il une convention, une classe de base ou un attribut que vous aimeriez appliquer? Une fois que vous vous êtes lancé dans la spécification explicite de tous les mappages, je ne sais pas quelle valeur vous obtiendriez d'AutoMapper.

Jimmy Bogard
la source
Jimmy, vous avez un point sur l'explicitness. Quant à la façon de réaliser cela de la manière la plus élégante: les classes et les attributs de base ne fonctionneraient pas dans cette situation, car les classes cibles ne sont pas vraiment sous mon contrôle - elles sont générées automatiquement à partir du contrat de données XSD, donc on aurait pour modifier manuellement ce code après chaque cycle de génération. Je suppose que la solution dépend d'un cas concret. Peut-être qu'une interface fluide similaire à celle de Windsor Castle permet de sélectionner les composants à enregistrer dans le conteneur pourrait être une solution?
Igor Brejc
Ah ça a plus de sens maintenant. C'est une fonctionnalité intéressante, je vais l'examiner dans le calendrier 2.1.
Jimmy Bogard
2
Que diriez-vous simplement d'avoir une valeur configurable où vous pouvez «ignorer» tous les champs non existants.
Ricardo Sanchez
6
Ce n'est pas une réponse à la question.
user2864740
Salut Jimmy, c'est toi l'auteur, n'est-ce pas? Je voudrais pouvoir ignorer toutes les propriétés non existantes étant un comportement par défaut (peut être contrôlé par un indicateur). De plus, je rencontre une erreur étrange d'AutoMapper que je ne parviens pas à comprendre. Cela ne me donne aucun détail.
Naomi
7

Cela semble être une vieille question, mais j'ai pensé publier ma réponse pour toute autre personne qui ressemblerait à moi.

J'utilise ConstructUsing, l'initialiseur d'objet couplé à ForAllMembers ignore par exemple

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

La seule infraction concernant l'ignorance de nombreux membres est ce fil - http://groups.google.com/group/automapper-users/browse_thread/thread/9928ce9f2ffa641f . Je pense que vous pouvez utiliser l'astuce utilisée dans ProvidingCommonBaseClassConfiguration pour ignorer les propriétés communes de classes similaires.
Et il n'y a aucune information sur la fonctionnalité "Ignorer le reste". J'ai déjà regardé le code et il me semble qu'il sera très et très difficile d'ajouter une telle fonctionnalité. Vous pouvez également essayer d'utiliser un attribut et marquer avec lui les propriétés ignorées et ajouter du code générique / commun pour ignorer toutes les propriétés marquées.

zihotki
la source
1
Une façon serait peut-être d'utiliser la méthode ForAllMembers et d'implémenter ma propre IMemberConfigurationExpression qui reçoit une chaîne contenant les noms de propriété de ces propriétés qui ne doivent pas être ignorées, puis de parcourir les autres et d'appeler Ignore (). Juste une idée, je ne sais pas si cela fonctionnerait.
Igor Brejc
Oui, cela peut aussi fonctionner, mais cette méthode est plus délicate que l'utilisation d'attributs mais elle offre plus de flexibilité. Dommage qu'il n'y ait pas de
solution
1

Je sais que c'est une vieille question, mais @jmoerdyk dans votre question:

Comment utiliseriez-vous cela dans une expression CreateMap () chaînée dans un profil?

vous pouvez utiliser cette réponse comme ceci dans le profil ctor

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

Vous pouvez utiliser ForAllMembers, que remplacer uniquement nécessaire comme ceci

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

Attention, il ignorera tout, et si vous n'ajoutez pas de mappage personnalisé, ils sont déjà ignorés et ne fonctionneront pas

aussi, je veux dire, si vous avez un test unitaire pour AutoMapper. Et vous testez que tous les modèles avec toutes les propriétés mappées correctement, vous ne devez pas utiliser une telle méthode d'extension

vous devez écrire explicitement ignorer

Anatoli Klamer
la source
-1

La solution actuelle (version 9) pour ignorer les propriétés qui n'existent pas dans le type de destination consiste à créer un mappage inversé et à l'inverser:

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

Dans la version 3.3.1, vous pouvez simplement utiliser les méthodes IgnoreAllPropertiesWithAnInaccessibleSetter()ou IgnoreAllSourcePropertiesWithAnInaccessibleSetter().

Ivan Kochurkin
la source
6
Cela ne fonctionne pas selon la question de l'affiche originale. Ces méthodes ignorent uniquement les propriétés protégées ou privées, pas les propriétés manquantes dans la source mais présentes dans le type de destination.
Dan