Automapper Custom Mapping Exception

17,302

Solution 1

I finally ended up getting this to work on my own. The code I ended up using is posted below. Creating the map of objects in the correct order proved to be important. I learned a lot fighting through this thing.

I've organized my mappings into a profile, which I won't get into here, suffice to say that if you can use my example outside of a class inheriting from the AutoMapper Profile class, you'll want to use Mapper.CreateMap instead of just Create Map.

 private void CreateMaps()
    {

        CreateMap<ContactEntity, TransfereeEntityDto>();

        //ContactEntity Mapping
        CreateMap<ContactEntity, TransfereeEntityDto>();

        //CustomerEntity Mapping
        CreateMap<CustomerEntity, CustomerEntityDto>();

        //AddressEntity Mapping
        CreateMap<AddressEntity, AddressEntityDto>();

        //ServiceEntity Mapping
        CreateMap<ServiceEntity, ServiceEntityDto>()
          .ForMember(dto => dto.ServiceTypeCode, opt => opt.MapFrom(source => source.TypeCode))
          .ForMember(dto => dto.ServiceDescriptionCode, opt => opt.MapFrom(source => source.DescriptionCode))
          .ForMember(dest => dest.SourceSystemName, opt => opt.ResolveUsing<SourceSystemNameResolver>().FromMember(entity => entity.SourceSystemName));


        //VehicleEntity Mapping
        CreateMap<VehicleEntity, VehicleEntityDto>()
            .ForMember(dest => dest.SourceSystemName, opt => opt.ResolveUsing<SourceSystemNameResolver>().FromMember(entity => entity.SourceSystemName))
            .ForMember(dto => dto.PortalId, option => option.Ignore());  //TODO: Should PortalID be mapped to anything? It is not in the entity.

        //ContentEntity Mapping
        CreateMap<ContentEntity, ContentEntityDto>()
            .ForMember(dest => dest.SourceSystemName, opt => opt.ResolveUsing<SourceSystemNameResolver>().FromMember(entity => entity.SourceSystemName));

        //OrderForwardingEntity Mapping
        CreateMap<OrderForwardingEntity, OrderForwardingEntityDto>();

        //ContainerEntity Mapping
        CreateMap<ContainerEntity, ContainerEntityDto>()
            .ForMember(dest => dest.SourceSystemName, opt => opt.ResolveUsing<SourceSystemNameResolver>().FromMember(entity => entity.SourceSystemName));

        //ShipmentForwardingEntity Mapping
        CreateMap<ShipmentForwardingEntity, ShipmentForwardingEntityDto>();


        //ShipmentRouting Mapping
        CreateMap<ShipmentRoutingEntity, ShipmentRoutingEntityDto>();

        //ShipmentEntity Mapping
        CreateMap<ShipmentEntity, ShipmentEntityDto>()
            .ForMember(dest => dest.SourceSystemName, opt => opt.ResolveUsing<SourceSystemNameResolver>().FromMember(entity => entity.SourceSystemName))
            .ForMember(dto => dto.Services, option => option.MapFrom(source => source.ServiceEntities));

        //Forwarder mapping
        CreateMap<ContactEntity, ForwarderEntityDto>();
        //TODO: This property doesn't have any properties in the data contract

        //OrderEntity Mapping
        CreateMap<OrderEntity, OrderEntityDto>()
            .ForMember(dest => dest.SourceSystemName,
                       opt => opt.ResolveUsing<SourceSystemNameResolver>().FromMember(entity => entity.SourceSystemName));
            //.ForMember(dto => dto.Forwarder, option => option.MapFrom(entity=>entity.Forwarder)

        //MoveEntityMapping
        CreateMap<MoveEntity, MoveEntityDto>()
            .ForMember(dto => dto.SourceSystemName, opt => opt.ResolveUsing<SourceSystemNameResolver>().FromMember(entity => entity.SourceSystemName));

    }

Solution 2

I know you already got this working but Im going to throw this out there in case other people land here.

In AutoMapper, when you have nested objects that need to be mapped, even if they are exactly the same (ie. a contract class and a model class that match), you have to define maps for the child classes, then when defining the map for the parent, inside the '.ForMember' option you can use those child maps to map the parent. I know this may sound confusing but an example will make it clear.

Say you have a the following:

namespace Contracts.Entities
{
    public class Person
    {
        public string FirstName {get; set;}

        public string LastName {get; set;}

        public Address Address {get; set;}        
    }

    public class Address
    {
        public string Street {get; set;}

        public string City {get; set;}

        public string State {get; set;}        
    }
}

namespace Model.Entities
{
    public class Person
    {
        public string FirstName {get; set;}

        public string LastName {get; set;}

        public Address Address {get; set;}        
    }

    public class Address
    {
        public string Street {get; set;}

        public string City {get; set;}

        public string State {get; set;}        
    }
}

Then you go and define the following maps:

  Mapper.CreateMap<Contracts.Entities.Person, Model.Entities.Person>();
  Mapper.CreateMap<Contracts.Entities.Address, Model.Entities.Address>();

You may think that AutoMapper would know to use the Address map when mapping a Contract person to a model person but it does not. Instead, here is what you have to do:

      Mapper.CreateMap<Contracts.Entities.Person, Model.Entities.Person>()
                    .ForMember(dest => dest.Address, opt => opt.MapFrom(src => Mapper.Map<Contracts.Entities.Address, Model.Entities.Address>(src.Address)));

 Mapper.CreateMap<Contracts.Entities.Address, Model.Entities.Address>();

So in your case you could define a Mapper.CreateMap<ContactEntity,TransfereeEntity>() map then call that map in the same way as address above when defining the map for PrimaryOriginTransferee. I.E.

 Mapper.CreateMap<MoveEntity, MoveEntityDto>()
 .ForMember(dest => dest.PrimaryOriginTransferee , opt => opt.MapFrom(src => Mapper.Map<ContactEntity,TransfereeEntity>(src.PrimaryOriginTransferee )));

Hope this helps someone!

Solution 3

You need to add mapping configurations for types of properties where type of target property is different to type of destination property.

    Mapper.CreateMap<ContactEntity, TransfereeEntity>();
Share:
17,302
letsgetsilly
Author by

letsgetsilly

Updated on June 09, 2022

Comments

  • letsgetsilly
    letsgetsilly almost 2 years

    Update 1-13-10 I've been able to find some success using the code below for mapping. I am essentially ignoring any of the properties that do not have a mapping and mapping them afterwards. I would appreciate feedback as to whether or not I am going about this in the best way possible. In addition, I am not sure how to go about unit testing this mapping. It was my impression that using the AutoMapper should help alleviate tediousness of checking each property.

    Here is my new code:

    Mapper.CreateMap<MoveEntity, MoveEntityDto>()
               .ForMember(dest => dest.PrimaryOriginTransferee, opt => opt.Ignore())
               .ForMember(dest => dest.PrimaryDestinationTransferee, opt => opt.Ignore())
               .ForMember(dest => dest.Customer, opt => opt.Ignore())
               .ForMember(dest => dest.DestinationAddress, opt => opt.Ignore())
               .ForMember(dest => dest.OriginAddress, opt => opt.Ignore())
               .ForMember(dest => dest.Order, opt => opt.Ignore())
               .ForMember(dest => dest.Shipment, opt => opt.Ignore())
               .ForMember(dest => dest.SourceSystemName, opt => opt.Ignore());
    
            Mapper.CreateMap<ContactEntity, TransfereeEntityDto>();
            Mapper.CreateMap<CustomerEntity, CustomerEntityDto>();
            Mapper.CreateMap<AddressEntity, AddressEntityDto>();
            Mapper.CreateMap<OrderEntity, OrderEntityDto>()
                .ForMember(dest => dest.OrderForwarding, opt => opt.Ignore())
                .ForMember(dest => dest.Forwarder, opt => opt.Ignore());
            Mapper.CreateMap<ShipmentEntity, ShipmentEntityDto>()
                .ForMember(dest => dest.Services, opt => opt.Ignore());
            Mapper.CreateMap<ServiceEntity, ServiceEntityDto>()
                .ForMember(dest => dest.ServiceTypeCode, opt => opt.Ignore()) //TODO: ServiceTypeCode not being mapped, should it?
                .ForMember(dest => dest.SourceSystemName, opt => opt.MapFrom(src => Enum.GetName(typeof(SourceSystemName), src.SourceSystemName)));
            Mapper.CreateMap<OrderForwardingEntity, OrderForwardingEntityDto>();
    
    
            Mapper.AssertConfigurationIsValid();
    
    
            MoveEntityDto moveEntityDto = Mapper.Map<MoveEntity, MoveEntityDto>(moveEntity);
            moveEntityDto.PrimaryDestinationTransferee = Mapper.Map<ContactEntity, TransfereeEntityDto>(moveEntity.PrimaryDestinationTransferee);
            moveEntityDto.PrimaryOriginTransferee = Mapper.Map<ContactEntity, TransfereeEntityDto>(moveEntity.PrimaryOriginTransferee);
            moveEntityDto.Customer = Mapper.Map<CustomerEntity, CustomerEntityDto>(moveEntity.Customer);
            moveEntityDto.DestinationAddress = Mapper.Map<AddressEntity, AddressEntityDto>(moveEntity.DestinationAddress);
            moveEntityDto.OriginAddress = Mapper.Map<AddressEntity, AddressEntityDto>(moveEntity.OriginAddress);
            moveEntityDto.Order = Mapper.Map<OrderEntity, OrderEntityDto>(moveEntity.Order);
            moveEntityDto.Order.OrderForwarding = Mapper.Map<OrderForwardingEntity, OrderForwardingEntityDto>(moveEntity.Order.OrderForwarding);
            //moveEntityDto.Order.Forwarder = Mapper.Map<ForwarderEntity, ForwarderEntityDto>(moveEntity.Order.Forwarder);  //Apparently there is no forwarder entity for an Order
            moveEntityDto.Shipment = Mapper.Map<ShipmentEntity, ShipmentEntityDto>(moveEntity.Shipment);
            moveEntityDto.Shipment.Services = Mapper.Map<ServiceEntity[], ServiceEntityDto[]>(moveEntity.Shipment.ServiceEntities);
    

    Original Post:

    I'm attempting to use AutoMapper for the first time in order to map from a Bussiness Object to a DTO. I am running into issues that I do not know how to troubleshoot, including the following exception:

    AutoMapper.AutoMapperMappingException: Trying to map Graebel.SP.BO.MoveEntity to Graebel.SOA.Contracts.DataContracts.SP.MoveEntity. Exception of type 'AutoMapper.AutoMapperMappingException' was thrown

    Here is the AutoMapper Code that I am running:

    public MoveEntityDto MapMoveEntityToMoveEntityDto(MoveEntity moveEntity)
        {
            Mapper.CreateMap<MoveEntity, MoveEntityDto>()
                .ForMember(dest => dest.PrimaryOriginTransferee, opt => opt.MapFrom(src => src.PrimaryOriginTransferee))
                .ForMember(dest => dest.PrimaryDestinationTransferee,opt => opt.MapFrom(src => src.PrimaryDestinationTransferee))
                .ForMember(dest => dest.Customer, opt => opt.MapFrom(src => src.Customer))
                .ForMember(dest => dest.DestinationAddress, opt => opt.MapFrom(src => src.DestinationAddress))
                .ForMember(dest => dest.Order, opt => opt.MapFrom(src => src.Order))
                .ForMember(dest => dest.OriginAddress, opt => opt.MapFrom(src => src.OriginAddress))
                .ForMember(dest => dest.Shipment, opt => opt.MapFrom(src => src.Shipment))
                .ForMember(dest => dest.SourceSystemName, opt => opt.Ignore());
    
            Mapper.AssertConfigurationIsValid();
            MoveEntityDto moveEntityDto = Mapper.Map<MoveEntity, MoveEntityDto>(moveEntity);
    
            return moveEntityDto;
        }
    

    Here is the DTO (MoveEntityDto) that I am attempting to map:

    public class MoveEntityDto
    {       
        public bool IsOrderDetailPageModified { get; set; }  
        public bool IsRoutingPageModified { get; set; }  
        public bool IsServicePageModified { get; set; }  
        public bool IsContentAndContainerPageModified { get; set; }   
        public string FamilyRange { get; set; }  
        public string Office { get; set; }
        public string ActivityType { get; set; }
        public string ActivitySubject { get; set; }
        public string ActivityNote { get; set; }
        public TransfereeEntity PrimaryOriginTransferee { get; set; }
        public TransfereeEntity PrimaryDestinationTransferee { get; set; }
        public CustomerEntity Customer { get; set; }
        public AddressEntity OriginAddress { get; set; }
        public AddressEntity DestinationAddress { get; set; }
        public OrderEntity Order { get; set; }
        public ShipmentEntity Shipment { get; set; }
        public string PortalId { get; set; }
        public string SourceSystemId { get; set; }
        public EnterpriseEnums.SourceSystemName SourceSystemName { get; set; }
    
        public MoveEntity()
        {
            PrimaryOriginTransferee = new TransfereeEntity();
            PrimaryDestinationTransferee = new TransfereeEntity();
            Customer = new CustomerEntity();
            OriginAddress = new AddressEntity();
            DestinationAddress = new AddressEntity();
            Order = new OrderEntity();
            Shipment = new ShipmentEntity();
        }
    
        public bool HasShipment()
        {
            if (Shipment.ExternalShipmentId > 0)
            {
                return true;
            }
            return false;
        }
     }
    

    Here is the Business Object (MoveEntity) that I am trying to map from

     public class MoveEntity
    {
        public int SourceId { get; set; }
        public int MoveId { get; set; }
        public bool IsOrderDetailPageModified { get; set; }  // TODO: Internal -  Remove from data contract
        public bool IsRoutingPageModified { get; set; }  // TODO: Internal -  Remove from data contract
        public bool IsServicePageModified { get; set; }  // TODO: Internal -  Remove from data contract
        public bool IsContentAndContainerPageModified { get; set; }  // Rmove from data contract
        public string FamilyRange { get; set; } // TODO: Is this being used?
        public string Office { get; set; }
        public string ActivityType { get; set; }
        public string ActivitySubject { get; set; }
        public string ActivityNote { get; set; }
        public ContactEntity PrimaryOriginTransferee { get; set; }
        public ContactEntity PrimaryDestinationTransferee { get; set; }
        public CustomerEntity Customer { get; set; }
        public AddressEntity OriginAddress { get; set; }
        public AddressEntity DestinationAddress { get; set; }
        public OrderEntity Order { get; set; }
        public ShipmentEntity Shipment { get; set; }
        public string CreatedBy { get; set; }
        public DateTime CreatedDate { get; set; }
        public string ModifiedBy { get; set; }
        public DateTime ModifiedDate { get; set; }
        public string SourceSystemId { get; set; }
        public string SourceSystemName { get; set; }
        public string Version { get; set; }
        public string PortalId { get; set; }
    
        public MoveEntity()
        {
            PrimaryOriginTransferee = new ContactEntity
            {
                ContactTypeId = ContactEntity.ContactType.PrimaryOriginationTransferee
            };
    
            PrimaryDestinationTransferee = new ContactEntity
            {
                ContactTypeId = ContactEntity.ContactType.PrimaryDestinationTransferee
            };
    
            OriginAddress = new AddressEntity
            {
                AddressTypeId = AddressEntity.AddressType.Origination
            };
    
            DestinationAddress = new AddressEntity
            {
                AddressTypeId = AddressEntity.AddressType.Destination
            };
    
            Order = new OrderEntity();
            Customer = new CustomerEntity();
            Shipment = new ShipmentEntity();
        }
    
        public bool HasShipment()
        {
            if (Shipment.ShipmentId > 0)
            {
                return true;
            }
            return false;
        }
    }
    

    The properties within each class almost match up perfectly by name, but their types are different. Therefore I have attempted to perform a custom mapping using the "MapFrom" expression. However, AutoMapper doesn't seem to be able to allow me to point from one object type to another without complain.

    I've also tried mapping property-to-property, with no luck. It looked something like this:

    .ForMember(dest => dest.PrimaryOriginTransferee.Email, opt => opt.MapFrom(src => src.PrimaryOriginTransferee.Email))
    

    However, when attempting this, I receive the following exeception:

    must resolve to top-level member. Parameter name: lambdaExpression.

    I have been finding the documentation available for AutoMapper difficult to follow. Can someone please point me in the right direction as to how to use this utility correctly?

    Thanks in advance for any help!

    Adam