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
Automapper is considerably faster when mapping a List<T> of objects on . NET Core (It's still slower on full . NET Framework).
By default, AutoMapper only recognizes public members. It can map to private setters, but will skip internal/private methods and properties if the entire property is private/internal.
So, the AutoMapper Ignore() method is used when you want to completely ignore the property in the mapping. The ignored property could be in either the source or the destination object.
Alternatively, AutoMapper supports convention based mapping of enum values in a separate package AutoMapper.
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));
}
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!
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With