AutoMapper - map to derived objects depend on cond

2019-07-30 15:06发布

问题:

I want to map source class to derived (from abstract) destination classes depend on value of some property.

I have the following source classes:

public partial class ApplicationDriver
{
    public virtual ICollection<ApplicationDriverEquipment> Equipments { get; set; }

}

public partial class ApplicationDriverEquipment
{
    public int Id { get; set; }
    [StringLength(256)]
    public string Make { get; set; }
    [StringLength(256)]
    public string Model { get; set; }
    [StringLength(256)]
    public string Year { get; set; }
    [StringLength(256)]
    public string VINNumber { get; set; }
    [StringLength(256)]
    public string PlateNumber { get; set; }
    [StringLength(256)]
    public string CurrentMileage { get; set; }
    [StringLength(256)]
    public string Length { get; set; }
    public string Type { get; set; }

    public int DriverId { get; set; }
    public virtual ApplicationDriver Driver { get; set; }
}

I want to map to the following classes, depend on Type parameter:

public class ApplicationDriverDomain
{
    public List<ApplicationDriverEquipmentAbstractDomain> Equipments { get; set; }

}

public abstract class ApplicationDriverEquipmentAbstractDomain
{
    public int Id { get; set; }
    public string Make { get; set; }
    public string Model { get; set; }
    public string Year { get; set; }
    public string PlateNumber { get; set; }
    public string CurrentMileage { get; set; }
    public string Type { get; protected set; }
}

public class ApplicationDriverEquipmentTractorDomain : ApplicationDriverEquipmentAbstractDomain
{
    public ApplicationDriverEquipmentTractorDomain()
    {
        Type = ApplicationDriverEquipmentTypeStaticStringsDomain.Tractor;
    }
    public string VINNumber { get; set; }
}

public class ApplicationDriverEquipmentTrailerDomain : ApplicationDriverEquipmentAbstractDomain
{
    public ApplicationDriverEquipmentTrailerDomain()
    {
        Type = ApplicationDriverEquipmentTypeStaticStringsDomain.Trailer;
    }

    public string Length { get; set; }
}

public class ApplicationDriverEquipmentStraightTruckDomain : ApplicationDriverEquipmentAbstractDomain
{
    public ApplicationDriverEquipmentStraightTruckDomain()
    {
        Type = ApplicationDriverEquipmentTypeStaticStringsDomain.StraightTruck;
    }

    public string VINNumber { get; set; }
    public string Length { get; set; }
}

public class ApplicationDriverEquipmentCargoVanDomain : ApplicationDriverEquipmentAbstractDomain
{
    public ApplicationDriverEquipmentCargoVanDomain()
    {
        Type = ApplicationDriverEquipmentTypeStaticStringsDomain.CargoVan;
    }

    public string VINNumber { get; set; }
    public string Length { get; set; }
}

I try to do it:

    ApplicationDriverEquipmentAbstractDomain GetEquipment(Infrastructure.Asset.ApplicationDriverEquipment infrastructure)
    {
        ApplicationDriverEquipmentAbstractDomain result = null;
        var config = new MapperConfiguration(cfg => cfg.AddProfile<AutoMapperApplicationModel>());
        var mapper = config.CreateMapper();

        switch (infrastructure.Type)
        {
            case ApplicationDriverEquipmentTypeStaticStringsDomain.Tractor:
                result = mapper.Map<ApplicationDriverEquipmentTractorDomain>(infrastructure);
                break;

            case ApplicationDriverEquipmentTypeStaticStringsDomain.Trailer:
                result = mapper.Map<ApplicationDriverEquipmentTrailerDomain>(infrastructure);
                break;

            case ApplicationDriverEquipmentTypeStaticStringsDomain.StraightTruck:
                result = mapper.Map<ApplicationDriverEquipmentStraightTruckDomain>(infrastructure);
                break;

            case ApplicationDriverEquipmentTypeStaticStringsDomain.CargoVan:
                result = mapper.Map<ApplicationDriverEquipmentCargoVanDomain>(infrastructure);
                break;

        }

        return result;
    }

        CreateMap<Infrastructure.Asset.ApplicationDriverEquipment, ApplicationDriverEquipmentTractorDomain>();
        CreateMap<Infrastructure.Asset.ApplicationDriverEquipment, ApplicationDriverEquipmentTrailerDomain>();
        CreateMap<Infrastructure.Asset.ApplicationDriverEquipment, ApplicationDriverEquipmentStraightTruckDomain>();
        CreateMap<Infrastructure.Asset.ApplicationDriverEquipment, ApplicationDriverEquipmentCargoVanDomain>();

        CreateMap<Infrastructure.Asset.ApplicationDriverEquipment, ApplicationDriverEquipmentAbstractDomain>()
            .Include<Infrastructure.Asset.ApplicationDriverEquipment, ApplicationDriverEquipmentTractorDomain>()
            .Include<Infrastructure.Asset.ApplicationDriverEquipment, ApplicationDriverEquipmentTrailerDomain>()
            .Include<Infrastructure.Asset.ApplicationDriverEquipment, ApplicationDriverEquipmentStraightTruckDomain>()
            .Include<Infrastructure.Asset.ApplicationDriverEquipment, ApplicationDriverEquipmentCargoVanDomain>()
            .ForMember(dest => dest.Type, opt => opt.ResolveUsing(GetEquipment))
            ;

        CreateMap<Infrastructure.Asset.ApplicationDriver, ApplicationDriverDomain>()
            .ForMember(dest => dest.Equipments, opt => opt.MapFrom(src => src.Equipments));

but I got an error:

"Error mapping types.\r\n\r\nMapping types:\r\nApplicationDriver -> ApplicationDriverDomain\r\nInfrastructure.Asset.ApplicationDriver -> Domain.POCO.Application.ApplicationDriverDomain\r\n\r\nType Map configuration:\r\nApplicationDriver -> ApplicationDriverDomain\r\nInfrastructure.Asset.ApplicationDriver -> Domain.POCO.Application.ApplicationDriverDomain\r\n\r\nProperty:\r\nEquipments"

回答1:

Inheritance in AM works by checking the type of the source, not by using a discriminator. That's what you were supposed to understand from the docs. One way to solve your problem is to pass an existing destination to Map. Created by smth like the GetEquipment method you have there. ApplyBaseMapping is a hack, you use Include/IncludeBase to reuse configuration. Unfortunately you've also hit a bug already fixed in the MyGet build, so the real error was kind of hidden from you. The only way to debug this in your version is by checking the execution plan.



回答2:

Updated:

So I believe I understand what you are trying to do, and apologies I may have slightly led you down the incorrect route. You flow is basically to distinguish what infrastructure type the source object is and then create that type of object. Also you need to understand the two different Mapper set up ways.

In the first part of your code you are trying to set it up with an instance of the Mapper but then using my Static style of using the Mapper.Map I would recommend always using the static style so that you have the ability to do some more dynamic ways of pulling mapping profiles in.

Mapper.Initialize(cfg => cfg.AddProfile<AutomapperRules>());
var domain = Mapper.Map<Domain.ApplicationDriverEquipmentTractorDomain>(inf);

Next you only need to reference that a mapping type from the underlying source to the domain types in your profile i.e.

CreateMap<ApplicationDriverEquipmentInfrastructure, ApplicationDriverEquipmentTractorDomain>();
CreateMap<ApplicationDriverEquipmentInfrastructure, ApplicationDriverEquipmentTrailerDomain>();
CreateMap<ApplicationDriverEquipmentInfrastructure, ApplicationDriverEquipmentStraightTruckDomain>();
CreateMap<ApplicationDriverEquipmentInfrastructure, ApplicationDriverEquipmentCargoVanDomain>();

Then what you need to do is to call your GetEquipment method from the mapping that describes the ApplicationDriver i.e.

CreateMap<ApplicationDriver, ApplicationDriverDomain>()
            .ForMember(dest => dest.Equipments, opt => opt.ResolveUsing(x => x.Equipments.Select(GetEquipment)));

private ApplicationDriverEquipmentAbstractDomain GetEquipment(ApplicationDriverEquipmentInfrastructure infrastructure)
    {
        switch (infrastructure.Type)
        {
            case "Tractor":
                return Mapper.Map<ApplicationDriverEquipmentTractorDomain>(infrastructure);
            case "Trailer":
                return Mapper.Map<ApplicationDriverEquipmentTrailerDomain>(infrastructure);
            case "StraightTruck":
                return Mapper.Map<ApplicationDriverEquipmentStraightTruckDomain>(infrastructure);
            case "CargoVan":
                return Mapper.Map<ApplicationDriverEquipmentCargoVanDomain>(infrastructure);
        }
        return null;
    }

Example Usage:

Mapper.Initialize(cfg => cfg.AddProfile<AutomapperRules>());

var inf = new ApplicationDriverEquipmentInfrastructure()
{
     CurrentMileage = "mil",
     Length = "123",
     Make = "ccc",
     Model = "15",
     Type = "Tractor",
     VINNumber = "vin"
};

var driver = new ApplicationDriver()
{
     Equipments = new List<ApplicationDriverEquipmentInfrastructure>() {inf}
};

var domain = Mapper.Map<ApplicationDriverDomain>(driver);