[c#] AutoMapper는 여러 소스에서 변환

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

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

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

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



답변

여러 소스를 단일 대상에 직접 매핑 할 수 없습니다 . 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);


답변

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


답변

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

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


답변

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

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

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


답변

소스 중 하나에서 대상 유형을 매핑해야하는 시나리오가 있고 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();


답변

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

당신이 두 개의 클래스를 매핑 할, 말할 수 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,
});


답변