Conditionally mapping one source type to two desti

2019-01-27 06:10发布

问题:

I have a source DTO like this

public class Member 
{
    public string MemberId {get;set;}
    public string MemberType {get;set;}
    public string Name {get;set;}
}

The member type can be "Person" or "Company".

And two destination classes like this

public class PersonMember 
{
    public int PersonMemberId {get;set;}
    public string Name {get;set;}
}

public class CompanyMember 
{
    public int CompanyMemberId {get;set;}
    public string Name {get;set;}
}

I want to use Automapper to check what the value of MemberType is in the source class and depending on that type, map to one of the two destination types.

I saw the example of conditionally mapping, but it maps the field it performs the conditional check on. I want to check the condition and map a different field.

var config = new MapperConfiguration(cfg => {
  cfg.CreateMap<Foo,Bar>()
    .ForMember(dest => dest.baz, opt => opt.Condition(src => (src.baz >= 0))); 
});

My goal is something like this -

cfg.CreateMap<Member, PersonMember>()
.ForMember(dest => PersonMember.PersonMemberId, opt => if the source.MemberType == "Person" perform mapping from MemberId, otherwise do nothing);

cfg.CreateMap<Member, CompanyMember>()
.ForMember(dest => CompanyMember.CompanyMemberId, opt => if the source.MemberType == "Company" perform mapping from MemberId, otherwise do nothing);

回答1:

With automapper you must specify return type on invocation mapper eg. mapper.Map<PersonMember>(member), this tells that return type is PersonMember so you can't return CompanyMember.

You can do something like this:

var configPerson = new MapperConfiguration(cfg => cfg.CreateMap<Member, PersonMember>());
var configCompany = new MapperConfiguration(cfg => cfg.CreateMap<Member, CompanyMember>());

PersonMember personMember = null;
CompanyMember companyMember = null;

switch (member.MemberType )
{
    case "PersonMember":
        var mapper = configPerson.CreateMapper();
        personMember = mapper.Map<PersonMember>(member);
        break;
    case "CompanyMember":
        var mapper = configCompany.CreateMapper();
        companyMember = mapper.Map<CompanyMember>(member);
        break;
    default:
        throw new Exception("Unknown type");
        break;
}

Or you can try Custom type converters with object as return type.



回答2:

Introduce some base class Member. Inherit PersonMember, CompanyMember from the new base class.

Then define these mappings:

cfg.CreateMap<Dto.Member, Member>()
    .ConstructUsing((memberDto, context) => {
    switch (memberDto.MemberType)
    {
        case "PersonMember":
            return context.Mapper.Map<PersonMember>(memberDto);
        case "CompanyMember":
            return context.Mapper.Map<CompanyMember>(memberDto);
        default:
            throw new ArgumentOutOfRangeException($"Unknown MemberType {memberDto.MemberType}");
    }
});

cfg.CreateMap<Dto.Member, PersonMember>()
    .ForMember(dest => PersonMember.PersonMemberId,
               opt => opt.MapFrom(src => src.MemberId));

cfg.CreateMap<Dto.Member, CompanyMember>()
    .ForMember(dest => CompanyMember.CompanyMemberId,
               opt => opt.MapFrom(src => src.MemberId));

Now you can map using _mapperInstance.Map<Member>(memberDto);



回答3:

I saw the example of conditionally mapping, but it maps the field it performs the conditional check on. I want to check the condition and map a different field.

Try using such config:

cfg.CreateMap<Member, PersonMember>()
    .ForMember(dest => PersonMember.PersonMemberId, opt => { 
        opt.Condition(src => src.MemberType == "Person");
        opt.MapFrom(src => src.MemberId);
     });
cfg.CreateMap<Member, CompanyMember>()
    .ForMember(dest => CompanyMember.CompanyMemberId, opt => { 
        opt.Condition(src => src.MemberType == "Company");
        opt.MapFrom(src => src.MemberId);
     });

In case you mapping from a non-compatible object Id field will be set to 0.



回答4:

For version 5 and above you could try below code:

using System;
using AutoMapper;

namespace AutoMapOneToMulti
{
    class Program
    {
        static void Main(string[] args)
        {
            RegisterMaps();

            var s = new Source { X = 1, Y = 2 };

            Console.WriteLine(s);
            Console.WriteLine(Mapper.Map<Source, Destination1>(s));
            Console.WriteLine(Mapper.Map<Source, Destination2>(s));

            Console.ReadLine();
        }
        static void RegisterMaps()
        {
            Mapper.Initialize(cfg => cfg.AddProfile<GeneralProfile>());
        }
    }
    public class GeneralProfile : Profile
    {
        public GeneralProfile()
        {
            CreateMap<Source, Destination1>();
            CreateMap<Source, Destination2>();
        }
    }
    public class Source
    {
        public int X { get; set; }

        public int Y { get; set; }

        public override string ToString()
        {
            return string.Format("Source = X : {0}, Y : {1}", X, Y);
        }
    }
    public class Destination1
    {
        public int X { get; set; }

        public int Y { get; set; }
        public override string ToString()
        {
            return string.Format("Destination1 = X : {0}, Y : {1}", X, Y);
        }
    }
    public class Destination2
    {
        public int X { get; set; }

        public int Y { get; set; }
        public override string ToString()
        {
            return string.Format("Destination2 = X : {0}, Y : {1}", X, Y);
        }
    }
}

And for version below 5 you could try this:

using System;
using AutoMapper;

namespace AutoMapOneToMulti
{
    class Program
    {
        static void Main(string[] args)
        {
            RegisterMaps();

            var s = new Source { X = 1, Y = 2 };

            Console.WriteLine(s);
            Console.WriteLine(Mapper.Map<Source, Destination1>(s));
            Console.WriteLine(Mapper.Map<Source, Destination2>(s));

            Console.ReadLine();
        }
        static void RegisterMaps()
        {
            Mapper.Initialize(cfg => cfg.AddProfile<GeneralProfile>());
        }
    }
    public class GeneralProfile : Profile
    {
        protected override void Configure()
        {
            CreateMap<Source, Destination1>();
            CreateMap<Source, Destination2>();
        }
    }
    public class Source
    {
        public int X { get; set; }

        public int Y { get; set; }

        public override string ToString()
        {
            return string.Format("Source = X : {0}, Y : {1}", X, Y);
        }
    }
    public class Destination1
    {
        public int X { get; set; }

        public int Y { get; set; }
        public override string ToString()
        {
            return string.Format("Destination1 = X : {0}, Y : {1}", X, Y);
        }
    }
    public class Destination2
    {
        public int X { get; set; }

        public int Y { get; set; }
        public override string ToString()
        {
            return string.Format("Destination2 = X : {0}, Y : {1}", X, Y);
        }
    }
}

If you want a dynamic function, use this extension:

public static dynamic DaynamicMap(this Source source)
{
    if (source.X == 1)
        return Mapper.Map<Destination1>(source);
    return Mapper.Map<Destination2>(source);
}

Console.WriteLine(new Source { X = 1, Y = 2 }.DaynamicMap());
Console.WriteLine(new Source { X = 2, Y = 2 }.DaynamicMap());