AutoMapper: „Zignorować resztę”?

205

Czy istnieje sposób, aby powiedzieć AutoMapperowi, aby zignorował wszystkie właściwości oprócz tych, które są jawnie zmapowane?

Mam zewnętrzne klasy DTO, które mogą ulec zmianie z zewnątrz i chcę uniknąć jawnego ignorowania każdej właściwości, ponieważ dodanie nowych właściwości spowoduje uszkodzenie funkcji (spowoduje wyjątki) podczas próby odwzorowania ich na własne obiekty.

Igor Brejc
źródło
1
z ValueInjecter valueinjecter.codeplex.com/documentation tworzysz ValueInjections, które mają algorytm mapowania i mapę między określonymi właściwościami, i nie dbają o resztę właściwości
Omu 5'10
24
Dla tych, którzy używają Automapper> wersja 5, przeskocz w dół, aby zobaczyć szczegóły odpowiedzi.ForAllOtherMembers(opts => opts.Ignore())
Jack Ukleja
@Schneider „.ForAllOtherMembers (opts => opts.Ignore ())” różni się od rozszerzenia „IgnoreAllNonExisting” tutaj, główna różnica polega na tym, że jeśli nie skonfigurowano jawnie właściwości, za pomocą „.ForAllOtherMembers (opts => opts.Ignore ( )) ”nic nie zostanie zmapowane. użyj „IgnoreAllNonExisting” bez jawnej właściwości config, nadal otrzymujesz pewne właściwości mapowane (właściwości o tej samej nazwie) o wartości.
Dragon
Tak. ForAllOtherMembers jest odpowiedzią. Odpowiedzi IgnoreUnmapped nie robią nic poza tym, że powodują przesłanie config-valid-assert, ponieważ niezapisane elementy są i tak ignorowane.
N73k
Warto zauważyć, że robiąc to, wyraźnie ukrywasz potencjalnie istotne lub ważne zmiany w mapowanych klasach. Posiadanie wyraźnych odwzorowań dla każdej właściwości pozostawia zepsuty test za każdym razem, gdy zmienia się odwzorowana klasa, co zmusza do właściwej oceny. (Biorąc pod uwagę, że masz sprawdzian podczas wykonywania AssertConfigurationIsValid()połączenia) Z tego powodu uważam, że „Ignoruj ​​resztę” jest antypatternem.
Arve Systad

Odpowiedzi:

83

Jest to metoda rozszerzenia, którą napisałem, która ignoruje wszystkie nieistniejące właściwości w miejscu docelowym. Nie jestem pewien, czy nadal będzie przydatny, ponieważ pytanie ma więcej niż dwa lata, ale natknąłem się na ten sam problem, ponieważ musiałem dodać wiele ręcznych połączeń Ignoruj.

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

Stosowanie:

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

AKTUALIZACJA : Najwyraźniej to nie działa poprawnie, jeśli masz niestandardowe mapowania, ponieważ je zastępują. Myślę, że to może nadal działać, jeśli najpierw wywołasz IgnoreAllNonExisting, a następnie niestandardowe odwzorowania później.

schdr ma rozwiązanie (jako odpowiedź na to pytanie), które wykorzystuje, Mapper.GetAllTypeMaps()aby dowiedzieć się, które właściwości nie są mapowane i automatycznie je ignorować. Wydaje mi się to bardziej niezawodnym rozwiązaniem.

Czy Gencer
źródło
Od jakiegoś czasu nie korzystam z AutoMappera, ale zaakceptuję twoją odpowiedź, jeśli zadziała dla ciebie :).
Igor Brejc,
2
Dzięki!! Uważam to za bardzo przydatne. Indywidualne ignorowanie właściwości było sprzeczne z celem używania automappera w mojej sytuacji.
Daniel Robinson,
Zobacz następną odpowiedź dla osoby, która nie ma problemu z nadpisaniem
Jason Coyne,
3
Ta metoda powinna być na natywnym kodzie autoMapper! Bardzo ładnie, dziękuje!
Felipe Oriani
2
Do Twojej wiadomości, Jimmy sam (autor AutoMapper) skomentował poniżej, że odpowiedź @ nazima jest poprawna dla wersji 5+
Worthy7
244

Z tego, co zrozumiałem, pytanie brzmiało, że w miejscu docelowym znajdują się pola, które nie mają zmapowanego pola w źródle, dlatego szukasz sposobów na zignorowanie tych niezamapowanych pól docelowych.

Zamiast implementować i używać tej metody rozszerzenia, możesz po prostu użyć

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

Teraz automapper wie, że musi tylko sprawdzić, czy wszystkie pola źródłowe są odwzorowane, ale nie na odwrót.

Możesz także użyć:

Mapper.CreateMap<sourceModel, destinationModel>(MemberList.Destination);  
Nazim Hafeez
źródło
10
Ta odpowiedź powinna mieć więcej głosów pozytywnych, może nawet być oznaczona jako odpowiedź. Rozwiązało to mój problem i podobnie MemberList.Destinationrozwiązałoby problem operacyjny.
Tedd Hansen
1
Nie zadziała, jeśli chcesz zignorować kilka właściwości zarówno źródłowych, jak i docelowych :)
RealWillyWoka
62
Dla każdego, kto przyjdzie później, TO JEST PRAWIDŁOWA ODPOWIEDŹ NA 5.0
Jimmy Bogard,
3
wygląda ładne, ale nie dla mnie .. Próbowałem źródłowy i docelowy, ale zachowuje skarżą się na tym samym obiekcie własności brakuje mapę
Sonic Duszę
1
Korzystanie z wersji 6.0.2 i to nie działa. Wszelkie właściwości, które nie są mapowane z miejsca docelowego na źródło, nadpisują właściwości w źródle wartościami zerowymi i zerowymi. Ponadto kod nie wyjaśnia, co robisz, zwłaszcza jeśli pracujesz w zespole. Dlatego bardzo nie lubię tego kodu i dlatego wolę takie słowa, jak sugerowana odpowiedź „IgnoreAllNonExisting”
sksallaj,
222

Zaktualizowałem rozszerzenie Can Gencer, aby nie zastępowało żadnych istniejących map.

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

Stosowanie:

Mapper.CreateMap<SourceType, DestinationType>()
                .ForMember(prop => x.Property, opt => opt.MapFrom(src => src.OtherProperty))
                .IgnoreAllNonExisting();
Robert Schroeder
źródło
4
+1, Dziękujemy za opublikowanie tego rozwiązania. Wymyślenie dziwnego błędu zajęło mi wiele godzin, gdy korzystam z rozwiązania w goo.gl/rG7SL , dopóki nie natknąłem się na ten post.
Nordin
3
Polecam metodę Yohanba poniżej. Istnieje kilka przypadków narożnych, że to nie działa, bo się pojawia.
Jon Barker,
3
Czy można to zrobić w AutoMapper 4.2? (To Mapper.GetAllTypeMaps()jest przestarzałe)
mrmashal
14
Dla wersji AutoMapper 5+ prostu zastąpić Mapper.GetAllTypeMaps()z Mapper.Configuration.GetAllTypeMaps(). Oto odniesienie github.com/AutoMapper/AutoMapper/issues/1252
Sergey G.
5
Dla nowych osób czytających to. Ta odpowiedź dotyczy AutoMappera 2 i w chwili pisania tego komentarza znajdujemy się w wersji 6. Jest to hack i znacznie czystszym sposobem jest użycie enum MemberList. Zobacz Github numer 1839 i lepsze rozwiązanie. github.com/AutoMapper/AutoMapper/issues/1839 Więc przykład: stackoverflow.com/a/31182390/3850405
Ogglas
83

Udało mi się to zrobić w następujący sposób:

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

Uwaga: używam AutoMapper v.2.0.

Yohanb
źródło
4
Dziękuję bardzo! To działa jak urok. najpierw próbowałem połączyć wywołania, ale ForAllMembers po prostu zwrócił void :(. Nie było oczywiste, że poprzedzające IgnoreAll można zmodyfikować później.
SeriousM
5
Też mi się nie podoba ... jeśli masz 50 członków, a chcesz zignorować 25 ... to jaki jest sens automapper, jeśli nadal musisz zignorować 25 członków. Jeśli nazwy pasują do siebie, a istnieją właściwości, które nie pasują do siebie ... dlaczego nie wyjaśnić, aby powiedzieć automapperowi, aby nie pasował do niezmapowanych właściwości i przekazując całe pisanie?
sksallaj,
71

Wersja 5.0.0-beta-1 programu AutoMapper wprowadza ForAllOtherMembersmetodę rozszerzenia, dzięki czemu możesz teraz to zrobić:

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

Należy pamiętać, że jawne mapowanie każdej właściwości ma tę zaletę, że nigdy nie wystąpią problemy z cichym niepowodzeniem mapowania, które pojawiają się, gdy zapomnisz o mapowaniu właściwości.

Być może w twoim przypadku rozsądne może być zignorowanie wszystkich innych członków i dodanie TODOznaku „wracaj”, aby wyrazić je wyraźnie po ustaleniu częstotliwości zmian w tej klasie.

ajbeaven
źródło
3
Zadziwiające, że trwało to do wersji 5. Spójrz, ile głosów i próbowałem odpowiedzi na to pytanie ... zastanawiam się coś złego w zarządzaniu Automapper?
Jack Ukleja
Dziękuję za to, zajęło mi chwilę przewinięcie do tego, ale to, ale działa idealnie.
cobolstinks,
2
Możesz nawet umieścić linię ForAllOtherMembers na pierwszym miejscu, a wszystko będzie działać tak samo, co jest dobre, jeśli masz jakąś konfigurację klasy podstawowej.
N73k
To jest teraz preferowane podejście. Zastanawiasz się, czy PO może zmienić przyjętą odpowiedź?
Chase Florell
1
Czy istnieje odpowiednik zignorowania właściwości w obiekcie źródłowym? Coś takiego ForAllOtherSourceMembers?
SuperJMN
44

Od wersji AutoMapper 5.0 .TypeMapwłaściwość nie IMappingExpressiondziała, co oznacza, że ​​rozwiązanie 4.2 już nie działa. Stworzyłem rozwiązanie, które korzysta z oryginalnej funkcjonalności, ale ma inną składnię:

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

Realizacja:

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
źródło
3
Jak użyłbyś tego w łańcuchowym CreateMap<TSource,TDest>()wyrażeniu w Profile?
jmoerdyk
2
Dzięki za to. Metoda GetUnmappedPropertyNames zwraca wszystkie niezmapowane nazwy właściwości, zarówno źródłowej, jak i docelowej, która wydaje się być uszkodzona na odwrotnej mapie, więc musiałem wprowadzić niewielką zmianę w IgnoreUnmapped, aby sprawdzić, czy niezapisana właściwość była w źródle lub miejscu docelowym i zignorować odpowiednio. Oto skrzypce demonstrujące problem i aktualizację: dotnetfiddle.net/vkRGJv
Mun
1
Zaktualizowałem swoją odpowiedź, aby uwzględnić twoje odkrycia - nie używam mapowań źródeł, więc nie spotkałem się z tym! Dzięki.
Richard
1
To nie działa na PCL bez dostępnego odbicia, GetProperty (propName) nie istnieje.
George Taskos
Nie rozumiem, jak to jest rozwiązanie pytania ani jak to w ogóle cokolwiek robi. Niezapisane właściwości zostaną już zignorowane - ponieważ są odwzorowane . Plakat powiedział „jak ignorować rekwizyty, jeśli nie są wyraźnie zmapowane”. Oznacza to, że jeśli mam Src.MyProp i Dest.MyProp, to mapowanie powinno zostać zignorowane, chyba że istnieje jawne wywołanie MapFrom i ForMember dla MyProp. Domyślne odwzorowanie należy zignorować. Jedyne, co robi to rozwiązanie, to sprawienie, aby polecenie config-valid-asert przeszło - czego i tak nie potrzebujesz, aby mapowanie działało.
N73k
17

Minęło kilka lat, odkąd pytanie zostało zadane, ale ta metoda rozszerzenia wydaje mi się czystsza, używając bieżącej wersji 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
źródło
16

Dla tych, którzy używają niestatycznego interfejsu API w wersji 4.2.0 i nowszej, można zastosować następującą metodę rozszerzenia (znajdującą się tutaj w AutoMapperExtensionsklasie):

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

Ważną rzeczą jest tutaj to, że po usunięciu statycznego interfejsu API kod taki, jak ten Mapper.FindTypeMapFor, przestanie działać, stąd użycie expression.TypeMappola.

nick_w
źródło
7
Od wersji 5.0 expression.TypeMapnie jest już dostępny. Oto moje rozwiązanie dla 5.0
Richard
Musiałem użyć, public static IMappingExpression<TSource, TDestination> IgnoreAllNonExisting<TSource, TDestination>(this IMappingExpression<TSource, TDestination> expression)aby naprawić problemy z typem.
Nick M
16

W przypadku programu Automapper 5.0, aby pominąć wszystkie niezapisane właściwości, wystarczy je umieścić

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

na końcu swojego profilu.

Na przykład:

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

W takim przypadku tylko pole Id dla obiektu wyjściowego zostanie rozwiązane, wszystkie pozostałe zostaną pominięte. Działa jak urok, wydaje się, że nie potrzebujemy już żadnych skomplikowanych rozszerzeń!

kadrowanie
źródło
10

Zaktualizowałem odpowiedź Roberta Schroedera na AutoMapper 4.2. Przy niestatycznych konfiguracjach mapowania nie możemy użyć Mapper.GetAllTypeMaps(), ale expressionzawiera odniesienie do wymaganych 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
źródło
Nie działa w AutoMapper 5.0. Właściwość .TypeMap na IMappingExpression jest niedostępna. Wersja 5. + zobacz rozszerzenia w odpowiedzi Richarda
Michael Freidgeim,
Współpracuje z poprawką 4.2
Leszek P.
8

Jak wolisz określić, że niektórzy członkowie będą ignorowani? Czy istnieje konwencja, klasa podstawowa lub atrybut, który chcesz zastosować? Gdy przejdziesz do firmy polegającej na jawnym określaniu wszystkich mapowań, nie jestem pewien, jaką wartość uzyskasz z AutoMappera.

Jimmy Bogard
źródło
Jimmy, masz rację co do jawności. Jeśli chodzi o sposób, w jaki sposób osiągnąć to w najbardziej elegancki sposób: klasy podstawowe i atrybuty nie działałyby w tej sytuacji, ponieważ klasy docelowe nie są tak naprawdę pod moją kontrolą - są one automatycznie generowane z kontraktu danych XSD, więc jeden miałby aby ręcznie edytować ten kod po każdym cyklu generowania. Myślę, że rozwiązanie zależy od konkretnego przypadku. Być może płynny interfejs podobny do tego w Windsor Castle zapewnia wybór komponentów, które należy zarejestrować w kontenerze, może być rozwiązaniem?
Igor Brejc
Ach, to ma teraz większy sens. To interesująca funkcja, przyjrzę się temu w ramach czasowych 2.1.
Jimmy Bogard
2
A może po prostu mieć konfigurowalną wartość, w której można „zignorować” wszystkie nieistniejące pola.
Ricardo Sanchez,
6
To nie jest odpowiedź na pytanie.
user2864740,
Cześć Jimmy, jesteś autorem, prawda? Chciałbym móc zignorować wszystkie nieistniejące właściwości będące zachowaniem domyślnym (może być kontrolowane przez flagę). Ponadto mam dziwny błąd z AutoMappera, którego nie jestem w stanie zrozumieć. Nie podaje mi żadnych szczegółów.
Naomi
7

To wydaje się stare pytanie, ale pomyślałem, że opublikuję odpowiedź dla każdego, kto wygląda tak jak ja.

Używam ConstructUsing, inicjator obiektów w połączeniu z ForAllMembers ignoruje np

    Mapper.CreateMap<Source, Target>()
        .ConstructUsing(
            f =>
                new Target
                    {
                        PropVal1 = f.PropVal1,
                        PropObj2 = Map<PropObj2Class>(f.PropObj2),
                        PropVal4 = f.PropVal4
                    })
        .ForAllMembers(a => a.Ignore());
gm1886
źródło
1

Jedyną informacją o ignorowaniu wielu członków jest ten wątek - http://groups.google.com/group/automapper-users/browse_thread/thread/9928ce9f2ffa641f . Myślę, że można użyć sztuczki zastosowanej w ProvidingCommonBaseClassConfiguration, aby zignorować wspólne właściwości podobnych klas.
I nie ma żadnych informacji na temat funkcji „Ignoruj ​​resztę”. Przeglądałem już kod i wydaje mi się, że bardzo trudno będzie dodać taką funkcjonalność. Możesz także spróbować użyć jakiegoś atrybutu i oznaczyć go ignorowanymi właściwościami oraz dodać jakiś ogólny / wspólny kod, aby zignorować wszystkie oznaczone właściwości.

zihotki
źródło
1
Być może jednym ze sposobów byłoby użycie metody ForAllMembers i zaimplementowanie własnego IMemberConfigurationExpression, który odbiera ciąg znaków zawierający nazwy właściwości tych właściwości, których nie należy ignorować, a następnie przejrzyj pozostałe i wywołaj Ignore (). To tylko pomysł, nie jestem pewien, czy to zadziała.
Igor Brejc
Tak, to też może działać, ale ta metoda jest trudniejsza niż używanie atrybutów, ale oferuje większą elastyczność. Szkoda, że ​​nie ma srebrnej kuli :(.
zihotki
1

Wiem, że to stare pytanie, ale @jmoerdyk w twoim pytaniu:

Jak użyłbyś tego w łańcuchowym wyrażeniu CreateMap () w profilu?

możesz użyć tej odpowiedzi w ten sposób w profilu ctor

this.IgnoreUnmapped();
CreateMap<TSource, Tdestination>(MemberList.Destination)
.ForMember(dest => dest.SomeProp, opt => opt.MapFrom(src => src.OtherProp));
j.loucao.silva
źródło
0

Możesz użyć ForAllMembers, aby zastąpić tylko potrzebne w ten sposób

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

Bądź ostrożny, zignoruje wszystko, a jeśli nie dodasz niestandardowego mapowania, są one już ignorowane i nie będą działać

również chcę powiedzieć, jeśli masz test jednostkowy dla AutoMapper. I testujesz, że wszystkie modele z poprawnie odwzorowanymi wszystkimi właściwościami nie powinny używać takiej metody rozszerzenia

powinieneś wyraźnie napisać ignoruj

Anatoli Klamer
źródło
-1

Obecne (wersja 9) rozwiązanie do ignorowania właściwości, które nie istnieją w typie docelowym, polega na utworzeniu odwróconego mapowania i odwróceniu go:

var config = new MapperConfiguration(cfg => {
  cfg.CreateMap<TheActualDestinationType, TheActualSourceType>().ReverseMap();
});
Simopaa
źródło
-2

W wersji 3.3.1 możesz po prostu używać IgnoreAllPropertiesWithAnInaccessibleSetter()lub IgnoreAllSourcePropertiesWithAnInaccessibleSetter()metod.

Ivan Kochurkin
źródło
6
To nie działa zgodnie z pytaniem oryginalnego plakatu. Te metody ignorują tylko właściwości chronione lub prywatne, a nie właściwości, których brakuje w źródle, ale występują w typie docelowym.
Dan