Angenommen, ich habe zwei Modellklassen:
public class People {
public string FirstName {get;set;}
public string LastName {get;set;}
}
Haben Sie auch eine Klasse Telefon:
public class Phone {
public string Number {get;set;}
}
Und ich möchte wie folgt in ein PeoplePhoneD konvertieren:
public class PeoplePhoneDto {
public string FirstName {get;set;}
public string LastName {get;set;}
public string PhoneNumber {get;set;}
}
Sagen wir in meinem Controller, den ich habe:
var people = repository.GetPeople(1);
var phone = repository.GetPhone(4);
// normally, without automapper I would made
return new PeoplePhoneDto(people, phone) ;
Ich kann anscheinend kein Beispiel für dieses Szenario finden. Ist das möglich ?
Hinweis: Das Beispiel ist nur für diese Frage nicht real.
c#
automapper
Bart Calixto
quelle
quelle
PeoplePhoneDto
einPeople
undPhone
Mitglied haben?Antworten:
Sie können nicht viele Quellen direkt einem einzelnen Ziel zuordnen. Sie sollten die Karten einzeln anwenden, wie in der Antwort von Andrew Whitaker beschrieben . Sie müssen also alle Zuordnungen definieren:
Erstellen Sie dann das Zielobjekt mit einer dieser Zuordnungen und wenden Sie andere Zuordnungen auf das erstellte Objekt an. Und dieser Schritt kann mit einer sehr einfachen Erweiterungsmethode vereinfacht werden:
public static TDestination Map<TSource, TDestination>( this TDestination destination, TSource source) { return Mapper.Map(source, destination); }
Die Verwendung ist sehr einfach:
var dto = Mapper.Map<PeoplePhoneDto>(people) .Map(phone);
quelle
Map
Erweiterungsmethode an dem Punkt sichtbar ist, an dem Sie das Mapping durchführen (dh die korrekte Verwendung der Direktive wird hinzugefügt)Sie könnten ein
Tuple
dafür verwenden:Falls Sie mehr Quellmodelle hätten, können Sie eine andere Darstellung (Liste, Wörterbuch oder etwas anderes) verwenden, die alle diese Modelle als Quelle zusammenfasst.
Der obige Code sollte vorzugsweise in einer AutoMapperConfiguration-Datei abgelegt, einmal und global festgelegt und dann gegebenenfalls verwendet werden.
AutoMapper unterstützt standardmäßig nur eine einzige Datenquelle. Es gibt also keine Möglichkeit, mehrere Quellen direkt festzulegen (ohne sie in eine Sammlung einzuschließen), denn wie würden wir dann wissen, was passiert, wenn beispielsweise zwei Quellmodelle Eigenschaften mit demselben Namen haben?
Es gibt jedoch einige Problemumgehungen, um dies zu erreichen:
public static class EntityMapper { public static T Map<T>(params object[] sources) where T : class { if (!sources.Any()) { return default(T); } var initialSource = sources[0]; var mappingResult = Map<T>(initialSource); // Now map the remaining source objects if (sources.Count() > 1) { Map(mappingResult, sources.Skip(1).ToArray()); } return mappingResult; } private static void Map(object destination, params object[] sources) { if (!sources.Any()) { return; } var destinationType = destination.GetType(); foreach (var source in sources) { var sourceType = source.GetType(); Mapper.Map(source, destination, sourceType, destinationType); } } private static T Map<T>(object source) where T : class { var destinationType = typeof(T); var sourceType = source.GetType(); var mappingResult = Mapper.Map(source, sourceType, destinationType); return mappingResult as T; } }
Und dann:
var peoplePhoneDto = EntityMapper.Map<PeoplePhoneDto>(people, phone);
Aber um ganz ehrlich zu sein, obwohl ich AutoMapper bereits seit einigen Jahren verwende, musste ich nie Mapping aus mehreren Quellen verwenden. In Fällen, in denen ich beispielsweise mehrere Geschäftsmodelle in meinem Einzelansichtsmodell benötigte, habe ich diese Modelle einfach in die Ansichtsmodellklasse eingebettet.
In Ihrem Fall würde es also so aussehen:
public class PeoplePhoneDto { public People People { get; set; } public Phone Phone { get; set; } }
quelle
PeoplePhoneDto
Sie vorgeschlagen haben, sieht gut aus, aber ich denke immer noch, dass die Zuordnung aus mehreren Quellen nützlich ist, insbesondere bei der Zuordnung von Ansichtsmodellen. Ich denke, die meisten realen Szenarien erfordern mehrere Quellen, um ein Ansichtsmodell zu erstellen. Ich nehme an, Sie könnten Ansichtsmodelle erstellen, die nicht abgeflacht sind, um das Problem zu umgehen, aber ich denke, es ist eine gute Idee, die Ansichtsmodelle zu erstellen, ohne sich darum zu kümmern, wie das Geschäftsschema aussieht.Tuple<People, Phone>
das gleiche wieTuple<Phone, People>
?Tuple
macht das erste Typargument alsItem1
, das zweite alsItem2
usw. verfügbar . In diesem Sinne ist die Reihenfolge wichtig.Vielleicht klingt es nach einem alten Beitrag, aber es gibt möglicherweise einige Leute, die immer noch mit demselben Problem zu kämpfen haben. In der Dokumentation zur AutoMapper IMapper Map-Funktion können wir dasselbe vorhandene Zielobjekt für die Zuordnung aus einer neuen Quelle wiederverwenden, vorausgesetzt, Sie haben bereits einen erstellt Wenn Sie für jede Quelle ein Ziel im Profil zuordnen, können Sie diese einfache Erweiterungsmethode verwenden:
public static class MappingExtentions { public static TDestination Map<TDestination>(this IMapper mapper, params object[] sources) where TDestination : new() { return Map(mapper, new TDestination(), sources); } public static TDestination Map<TDestination>(this IMapper mapper, TDestination destination, params object[] sources) where TDestination : new() { if (!sources.Any()) return destination; foreach (var src in sources) destination = mapper.Map(src, destination); return destination; } }
Bitte beachten Sie, dass ich eine Einschränkung für den Zieltyp erstellt habe, die besagt, dass es sich um einen instanziierbaren Typ handeln muss. Wenn Ihr Typ nicht so ist, verwenden Sie
default(TDestination)
stattdessennew TDestination()
.Warnung : Diese Art der Zuordnung ist manchmal etwas gefährlich, da die Eigenschaften der Zielzuordnung möglicherweise von mehreren Quellen überschrieben werden und das Nachverfolgen des Problems in größeren Apps Kopfschmerzen bereiten kann. Es gibt eine lose Problemumgehung, die Sie anwenden können. Sie können dies jedoch wie folgt tun wieder ist es überhaupt keine feste Lösung:
public class Person { public string FirstName { get; set; } public string LastName { get; set; } public string PhoneNumber { get; set; } } public class Contact { public string Address { get; set; } public string PhoneNumber { get; set; } public string Other{ get; set; } } public class PersonContact { public string FirstName { get; set; } public string LastName { get; set; } public string Address{ get; set; } public string PhoneNumber { get; set; } } public class PersonMappingProfile : MappingProfile { public PersonMappingProfile() { this.CreateMap<Person, PersonContact>(); this.CreateMap<Phone, PersonContact>() .ForMember(dest => dest.PhoneNumber, opt => opt.MapFrom((src, dest) => dest.PhoneNumber ?? src.PhoneNumber)) // apply mapping from source only if the phone number is not already there, this way you prevent overwriting already initialized props .ForAllOtherMembers(o => o.Ignore()); } }
quelle
Wenn Sie ein Szenario haben, in dem der Zieltyp aus einer der Quellen zugeordnet werden soll und Sie Linq-Projektionen verwenden möchten, können Sie Folgendes tun.
Ich brauchte dies hauptsächlich für Cross-Apply-Abfragen wie diese.
var dbQuery = from p in _context.People from ph in _context.Phones .Where(x => ...).Take(1) select ValueTuple.Create(p, ph); var list = await dbQuery .ProjectTo<PeoplePhoneDto>(_mapper.ConfigurationProvider) .ToListAsync();
quelle
Ich würde eine Erweiterungsmethode wie folgt schreiben:
public static TDestination Map<TSource1, TSource2, TDestination>( this IMapper mapper, TSource1 source1, TSource2 source2) { var destination = mapper.Map<TSource1, TDestination>(source1); return mapper.Map(source2, destination); }
Dann wäre die Verwendung:
quelle
Es gibt bereits viele Optionen, aber keine passt wirklich zu dem, was ich wollte. Ich bin letzte Nacht eingeschlafen und hatte den Gedanken:
Nehmen wir an, Sie möchten Ihre beiden Klassen zuordnen
People
undPhone
aufPeoplePhoneDto
public class People { public string FirstName {get;set;} public string LastName {get;set;} }
+
public class Phone { public string Number {get;set;} }
=
public class PeoplePhoneDto { public string FirstName {get;set;} public string LastName {get;set;} public string PhoneNumber {get;set;} }
Alles, was Sie wirklich brauchen, ist eine weitere Wrapper-Klasse für Automapper-Zwecke.
public class PeoplePhone { public People People {get;set;} public Phone Phone {get;set;} }
Und dann definieren Sie das Mapping:
Und benutze es
var dto = Map<PeoplePhoneDto>(new PeoplePhone { People = people, Phone = phone, });
quelle