AutoMapper는 여러 소스에서 변환


80

두 개의 모델 클래스가 있다고 가정 해 보겠습니다.

public class People {
   public string FirstName {get;set;}
   public string LastName {get;set;}
}

또한 수업 전화 :

public class Phone {
   public string Number {get;set;}
}

그리고 다음과 같이 PeoplePhoneDto로 변환하고 싶습니다.

public class PeoplePhoneDto {
    public string FirstName {get;set;}
    public string LastName {get;set;}
    public string PhoneNumber {get;set;}
}

내 컨트롤러에서 내가 가지고 있다고 가정 해 봅시다.

var people = repository.GetPeople(1);
var phone = repository.GetPhone(4);

// normally, without automapper I would made
return new PeoplePhoneDto(people, phone) ;

이 시나리오에 대한 예를 찾을 수없는 것 같습니다. 이것이 가능한가 ?

참고 :이 질문에 대한 예제는 실제가 아닙니다.


@Andrei는 비슷해 보이지만 해결하려는 문제의 차이입니다. 또한이 질문에 어떻게 적용되는지 이해하기 어렵습니다.
Bart Calixto 2014 년

왜하지 PeoplePhoneDtoPeoplePhone멤버?
분쇄

그것이 내가 폭로하고 싶은 것이 아니기 때문입니다.
Bart Calixto 2014 년

3
재 개설 투표-저는 stackoverflow.com/questions/12429210/… 이 중복 이라고 생각 하지만 (하나의 답변과 함께) 너무 현지화되어 표준으로 간주되지 않는 것 같습니다. 문제를 해결할만큼 충분히 대답하지 않은 경우 중복 질문이 계산되지 않는 선례가 있습니다.
Brilliand

답변:


103

여러 소스를 단일 대상에 직접 매핑 할 수 없습니다 . Andrew Whitaker 답변에 설명 된대로지도를 하나씩 적용해야합니다 . 따라서 모든 매핑을 정의해야합니다.

Mapper.CreateMap<People, PeoplePhoneDto>();
Mapper.CreateMap<Phone, PeoplePhoneDto>()
        .ForMember(d => d.PhoneNumber, a => a.MapFrom(s => s.Number));

그런 다음 이러한 매핑 중 하나를 사용하여 대상 개체를 만들고 생성 된 개체에 다른 매핑을 적용합니다. 이 단계는 매우 간단한 확장 방법으로 단순화 할 수 있습니다.

public static TDestination Map<TSource, TDestination>(
    this TDestination destination, TSource source)
{
    return Mapper.Map(source, destination);
}

사용법은 매우 간단합니다.

var dto = Mapper.Map<PeoplePhoneDto>(people)
                .Map(phone);

내가 사용하는 단일 대상에 여러 소스를 매핑하는 AutoMapper에 대한 추상화 IMapper 가 있습니다.
Ilya Palkin 2014

@Sergey Berezovskiy, 매핑을 생성하고 PeoplePhoneDto 클래스에 확장 메서드를 추가하고 사용량을 복사하여 붙여 넣었지만 (즉, 필요한 모든 것을 복사하여 붙여 넣었습니다) "No overload for method Map takes 1 argument"오류가 발생합니다. 내가 무엇을 놓치고 있습니까? Automapper 4.2.1을 사용하고 있습니다.
OfirD

당신 있는지 확인 @HeyJude Map확장 방법을 사용하여 매핑 할 점 (즉 지시어를 사용하여 추가 수정)에서 볼 수 있습니다
세르게이 Berezovskiy

이것은 좋지만, 나는 그것을 조롱 할 수 없기 때문에 정적 맵을 사용하고 싶지 않습니다. 그래서 나는 ilyas Imapper 추상화를 시도 할 것입니다
sensei

이렇게하면 각 맵에 대해 DTO 클래스가 2 번 생성 되나요? 아니면 한 번만 생성 되나요?
Anestis Kivranoglou

19

Tuple이것을 위해 사용할 수 있습니다 .

Mapper.CreateMap<Tuple<People, Phone>, PeoplePhoneDto>()
    .ForMember(d => d.FirstName, opt => opt.MapFrom(s => s.Item1.FirstName))
    .ForMember(d => d.LastName, opt => opt.MapFrom(s => s.Item1.LastName))
    .ForMember(d => d.Number, opt => opt.MapFrom(s => s.Item2.Number ));

더 많은 소스 모델이있는 경우 다른 표현 (목록, 사전 또는 기타)을 사용하여 이러한 모든 모델을 소스로 모을 수 있습니다.

위의 코드는 일부 AutoMapperConfiguration 파일에 배치하고 한 번 전체적으로 설정 한 다음 적용 가능한 경우 사용하는 것이 좋습니다.

AutoMapper는 기본적으로 단일 데이터 소스 만 지원합니다. 따라서 예를 들어 두 개의 소스 모델에 동일한 이름의 속성이있는 경우 어떻게 알 수 있습니까?

이를 달성하기위한 몇 가지 해결 방법이 있습니다.

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

그리고:

var peoplePhoneDto = EntityMapper.Map<PeoplePhoneDto>(people, phone);

그러나 솔직히 말해서 이미 몇 년 동안 AutoMapper를 사용하고 있지만 여러 소스의 매핑을 사용할 필요가 없었습니다. 예를 들어 단일 뷰 모델에 여러 비즈니스 모델이 필요한 경우 이러한 모델을 뷰 모델 클래스에 포함했습니다.

따라서 귀하의 경우에는 다음과 같이 보일 것입니다.

public class PeoplePhoneDto {
    public People People { get; set; }
    public Phone Phone { get; set; }
}

3
그래서 매핑을하기 전에 튜플을 만들어야합니다. 오토 매퍼의 진정한 이점이 무엇인지 궁금합니다. 다른 유형 (튜플, dic 등)을 만드는 것을 피할 수있는 방법이 있습니까?
Bart Calixto 2014 년

답변 해 주셔서 감사합니다. 자동 매퍼에 대해 많이 이해하게되었습니다. 문제는 API를 노출 할 때 '도메인 관련'문제를 소비자에게 전달하고 클라이언트가 중첩 된 유형없이 쉽게 소비 할 수 있도록하기 때문에 때때로 포함 된 모델이 바람직하지 않은 방식으로 데이터를 신중하게 모델링하는 것입니다. . 내 자신의 내부 사용을위한 것이었다면 확실히 임베디드 옵션으로 진행할 것입니다.
Bart Calixto 2014 년

1
PeoplePhoneDto당신은 외모 좋은 제안,하지만, 난 여전히 여러 소스에서 매핑 매핑 뷰 모델에서 특히 유용하다 생각합니다. 대부분의 실제 시나리오에서는 뷰 모델을 구성하기 위해 여러 소스가 필요하다고 생각합니다. 문제를 해결하기 위해 평면화되지 않은 뷰 모델을 만들 수 있다고 생각하지만 비즈니스 스키마가 어떻게 보이는지 신경 쓰지 않고 뷰 모델을 만드는 것이 좋습니다.
The Muffin Man

또한 automapper는 튜플에서 유형이 어떤 순서인지 신경 쓰나요? 이다 Tuple<People, Phone>은 동일 Tuple<Phone, People>?
The Muffin Man

2
@TheMuffinMan Tuple은 첫 번째 유형 인수를로 Item1, 두 번째는 Item2로 등 으로 노출합니다 . 그런 의미에서 순서가 중요합니다.
Josh M.

1

다음과 같이 확장 메서드를 작성합니다.

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

그러면 사용법은 다음과 같습니다.

    mapper.Map<People, Phone, PeoplePhoneDto>(people, phone);

1

아마도 오래된 게시물 처럼 들리지만 AutoMapper IMapper Map 함수 문서를 참조하여 동일한 문제로 여전히 어려움을 겪고있는 일부 사람들이있을 수 있습니다. 이미 만든 경우 새 소스에서 매핑을 위해 동일한 기존 대상 객체를 재사용 할 수 있습니다. 각 소스를 프로필의 대상에 매핑하면 다음과 같은 간단한 확장 방법을 사용할 수 있습니다.

인스턴스화 가능한 유형이어야한다는 대상 유형에 대한 제약 조건을 만들었습니다. 당신의 유형은 사용처럼이 아닌 경우 default(TDestination)대신 new TDestination().

경고 : 이러한 유형의 매핑은 대상 매핑 ​​속성이 여러 소스에 의해 덮어 쓰여지고 더 큰 앱에서 문제를 추적하는 것이 골칫거리가 될 수 있기 때문에 때때로 약간 위험합니다. 적용 할 수있는 느슨한 해결 방법이 있습니다. 아래와 같이 수행 할 수 있지만 다시 말하지만 그것은 전혀 고체 솔루션이 아닙니다.


0

소스 중 하나에서 대상 유형을 매핑해야하는 시나리오가 있고 linq 프로젝션을 사용하려는 경우 다음을 수행 할 수 있습니다.

    Mapper.CreateMap<People, PeoplePhoneDto>(MemberList.Source);
    Mapper.CreateMap<Phone, PeoplePhoneDto>(MemberList.Source)
          .ForMember(d => d.PhoneNumber, a => a.MapFrom(s => s.Number));

    CreateMap<PeoplePhoneDto,(People,Phone)>(MemberList.Destination)
           .ForMember(x => x.Item1, opts => opts.MapFrom(x => x))
           .ForMember(x => x.Item2, opts => opts.MapFrom(x => x.PhoneNumber))
           .ReverseMap();

나는 주로 이와 같은 교차 적용 쿼리에 필요했습니다.

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

0

이미 많은 옵션이 제공되었지만 내가 원하는 것에 맞는 옵션은 없습니다. 나는 어제 밤에 잠들었 고 생각했습니다.

당신이 두 개의 클래스를 매핑 할, 말할 수 PeoplePhonePeoplePhoneDto

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

정말 필요한 것은 Automapper 목적을위한 또 다른 래퍼 클래스입니다.

public class PeoplePhone {
    public People People {get;set;}
    public Phone Phone {get;set;}
}

그런 다음 매핑을 정의합니다.

CreateMap<PeoplePhone, PeoplePhoneDto>()

그리고 그것을 사용하십시오

var dto = Map<PeoplePhoneDto>(new PeoplePhone
{
    People = people,
    Phone = phone,
});
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.