AutoMapper: Store navigation properties to intermediate result for mapping - automapper

In my below mapping I have to check for the existing of the navigation property and based on it I have to map the properties
.ForMember(x => x.IsReceived, opt => opt.MapFrom(x => x.DropOff.Contamination.Any(c => c.EquipmentId == x.EquipmentId && c.IsValidated.GetValueOrDefault())))
.ForMember(x => x.ReceivedOn, opt => opt.MapFrom(x => x.DropOff.Contamination.Any(c => c.EquipmentId == x.EquipmentId && c.IsValidated.GetValueOrDefault()) ? x.DropOff.Contamination.FirstOrDefault(c => c.EquipmentId == x.EquipmentId && c.IsValidated.GetValueOrDefault()).MeasurementDate : null))
.ForMember(x => x.Receiver, opt => opt.MapFrom((x) => x.DropOff.Contamination.Any(c => c.EquipmentId == x.EquipmentId && c.IsValidated.GetValueOrDefault()) ? new LookupItem<string, string>
{
Id = x.DropOff.Contamination.Single(c => c.EquipmentId == x.EquipmentId && c.IsValidated.GetValueOrDefault()).ReceivedBy,
Value = FormattingHelper.GetStaffDisplayName(x.DropOff.Contamination.Single(c => c.EquipmentId == x.EquipmentId && c.IsValidated.GetValueOrDefault()).ReceivedByNavigation, null)
} : new LookupItem<string, string>()))
But you can see I have to repeat this object selection check every time for all the properties
x.DropOff.Contamination.Single(c => c.EquipmentId == x.EquipmentId && c.IsValidated.GetValueOrDefault())
Is there any way in auto mapper to store this object temporarily and use it for multiple properties rather selecting it this way everytime?

As suggested by #LucianBargaoanu in comment I used IncludeMembers and it worked perfectly!
This is how I implemented it now
I included the property like this with the original map definition
CreateMap<DropOffBarcodedEquipment, DropOffEquipmentBase>()
.IncludeMembers(x => x.DropOff.Contamination.FirstOrDefault(c => c.EquipmentId == x.EquipmentId && c.IsValidated.GetValueOrDefault()))
And then added mapping from Contamination to DropOffEquipmentBase
CreateMap<Contamination, DropOffEquipmentBase>()
.ForMember(x => x.IsReceived, opt => opt.MapFrom(x => x.MeasurementDate.HasValue))
.ForMember(x => x.ReceivedOn, opt => opt.MapFrom(x => x.MeasurementDate))
.ForMember(x => x.Receiver, opt => opt.MapFrom(x => new LookupItem<string, string>()
{
Id = x.ReceivedBy,
Value = FormattingHelper.GetStaffDisplayName(x.ReceivedByNavigation, null)
}));
And this combination works like a charm!
Thanks to #LucianBargaoanu

Related

AutoMapper ProjectTo not loading navigation properties for conditional mapping

I have the following mapping for a property
.ForMember(x => x.Destination, opt => opt.MapFrom(x =>
x.Request.PurposeId == (int)PurposeTypes.PickUpShipment
? x.Request.RequestDestinations.FirstOrDefault().Destination
: x.Request.ShippingDestinationDetails.FirstOrDefault(dd => dd.ShippingAddressTypeId == (int)ShippingRequestAddressType.Destination).ShippingAddress.Destination
))
And the destination object map is like this
CreateMap<Destination, DestinationDTO>()
.ForMember(x => x.LocationCode, opt => opt.MapFrom(x => x.LocationCode))
.ForMember(x => x.CountryCode, opt => opt.MapFrom(x => x.CountryCode))
.ForMember(x => x.CountryName, opt => opt.MapFrom(x => x.State.StateShortName)); // this is not working
The problem is when I am using ProjectTo the CountryName is not loading because it is not loading the navigation property State of the destination, so x.State.StateShortName is coming empty.
And I found out the reason behind it, this is happening because I am loading the destination conditionally and because of this it is not loading the navigation property, because if I remove the condition and loads the destination like this it works
.ForMember(x => x.Destination, opt => opt.MapFrom(x =>
x.Request.RequestDestinations.FirstOrDefault().Destination
))
So basically ProjectTo is not loading the navigation property if the property mapping is conditional and not direct.
Is there any way I can achieve this?

Automapper through relations in Entity Framework

As a newbie to automapper (v10.0.0) I'm trying to replace one of my queries. I currently use this to generate my response:
var query = from a in _context.ApprovalStatuses.AsNoTracking()
.Include(x => x.ApprovalOrder).ThenInclude(x => x.Worker)
where a.RequestId == request.Id
orderby a.ApprovalOrder.Position
let w = a.ApprovalOrder.Worker
select new RequestApprovalStatusDTO {
AssignedUtc = a.AssignedUtc,
Comments = a.Comments,
DecisionDateUtc = a.ApprovedDateUtc ?? a.RejectedDateUtc,
Email = w.Email,
Name = w.Name,
Uuid = a.Uuid
};
So I started by creating my mapping in my Profile subclass:
CreateMap<ApprovalStatus, RequestApprovalStatusDTO>()
.ForMember(x => x.DecisionDateUtc, x => x.MapFrom(y => y.ApprovedDateUtc ?? y.RejectedDateUtc))
.ForMember(x => x.Email, x => x.MapFrom(y => y.ApprovalOrder.Worker.Email))
.ForMember(x => x.Name, x => x.MapFrom(y => y.ApprovalOrder.Worker.Name));
And then I rewrote the query like so:
var query = _context.ApprovalStatuses
.Include(x => x.ApprovalOrder)
.ThenInclude(x => x.Worker)
.Where(x => x.RequestId == request.Id)
.OrderBy(x => x.ApprovalOrder.Position);
return Ok(_mapper.Map<RequestApprovalStatusDTO>(query));
At runtime, it's telling me
AutoMapperMappingException: Missing type map configuration or unsupported mapping.
Mapping types:
Object -> RequestApprovalStatusDTO
System.Object -> BoltOn.RequestApprovalStatusDTO
lambda_method(Closure , object , RequestApprovalStatusDTO , ResolutionContext )
I understand it's telling me that it doesn't know how to convert from object, but I'm not sure why it's trying to do that since query is an IOrderedQueryable<ApprovalStatus>.
Thanks to Lucian's pointer I was able to solve it like so:
var query = _context.ApprovalStatuses
.Where(x => x.Request.Uuid == uuid)
.OrderBy(x => x.ApprovalOrder.Position);
var approvals = await _mapper.ProjectTo<RequestApprovalStatusDTO>(query).ToArrayAsync();
if (approvals.Length == 0)
return NotFound();
return Ok(approvals);

Automapper - Mapping Multiple Properties using 1 Resolver

Mapper.CreateMap<WorkItemSummary, WorkItemSummaryDto> ()
.ForMember(dto => dto.ProductDisplayName, opt => opt.MapFrom(src => src.Product.DisplayName))
.ForMember(dto => dto.TeamId, opt => opt.MapFrom(src => src.Product.Team.TeamId))
.ForMember(dto => dto.TeamName, opt => opt.MapFrom(src => src.Product.Team.TeamName))
.ForMember(dto => dto.RRConsolidated, opt => opt.ResolveUsing<WIConsolidateResolveReasonResolver>())
.ForMember(dto => dto.NewBugsQuery, opt => opt.ResolveUsing<WIQueryNewBugsResolver>())
.ForMember(dto => dto.ResolvedBugsQuery, opt => opt.ResolveUsing<WIQueryResolvedResolver>())
.ForMember(dto => dto.ClosedBugsQuery, opt => opt.ResolveUsing<WIQueryClosedResolver>())
.ForMember(dto => dto.BacklogQuery, opt => opt.ResolveUsing<WIQueryBacklogResolver>())
.ForMember(dto => dto.ResolvedReasonFixQuery, opt => opt.ResolveUsing<WIQueryResolvedReasonFixResolver>());
--- Adding One Resolver Code and mostly all resolvers are doing same Job but pulling different content from Xml Nodes
public class WIQueryNewBugsResolver: ValueResolver<WorkItemSummary, string>
{
protected override string ResolveCore(WorkItemSummary source)
{
XmlDocument xmlDoc = new XmlDocument();
xmlDoc.Load(System.IO.Path.Combine(AppDomain.CurrentDomain.RelativeSearchPath, "WIsQuery.xml"));
....
}
}
I have multiple resolvers doing almost same job of reading some xmls and getting the data. Thinking of consolidating all those calls under one resolver.
ex: WIConsolidateResolveReasonResolver, WIQueryResolvedReasonFixResolver, WIQueryBacklogResolver
In this regard, can I call one resolver once and get all the other properties populated in one call.
Is there any other method call in place of ForMember
Thanks
use "forallmembers" method of automapper if same job you are doing.may be it will help for you.

Automapper ConfigurationExpression Condition

Quick question - whats the difference in the following?
This one works:
CreateMap<OrderResult, OrderViewModel>()
.ForMember(x => x.SoldTo, opt => opt.Ignore())
.ForMember(x => x.ShipTo, opt => opt.Ignore())
.ForMember(x => x.ShowPlaceOrder, opt => opt.MapFrom(c => c.Messages.Count == 0));
I would expect this one to do pretty much the same thing except "Mapper.AssertConfigurationIsValid();" fails on this one saying that "ShowPlaceOrder" is not mapped.
CreateMap<OrderResult, OrderViewModel>()
.ForMember(x => x.SoldTo, opt => opt.Ignore())
.ForMember(x => x.ShipTo, opt => opt.Ignore())
.ForMember(x => x.ShowPlaceOrder, opt => opt.Condition(c => c.Messages.Count == 0));
Thanks
Joe
You still need to provide a source for ShowPlaceOrder in case the condition is true. The "Condition" method takes a predicate to decide if the mapping should be done. I think your first example is more clear.

AutoMapper Sorting List

I have this mapping defined
Mapper.CreateMap<Telephone, TelephoneDTO>()
.ForMember(dto => dto.Extension, opt => opt.MapFrom(src => src.Extension))
.ForMember(dto => dto.Number, opt => opt.MapFrom(src => src.Number))
.ForMember(dto => dto.Type, opt => opt.MapFrom(src => src.TelephoneType.Id));
when i do
IList<TelephoneDTO> dtos = Mapper.Map<IList<Telephone>, IList<TelephoneDTO>>(tels);
i would like the list of TelephoneDTO to be sorted by the Type.
How can i do that ?
thanks
AutoMapper is used for mapping, not for sorting. You could sort the list once the mapping being done:
IList<TelephoneDTO> dtos = Mapper
.Map<IList<Telephone>, IList<TelephoneDTO>>(tels)
.OrderBy(x => x.Type)
.ToList();
or
IList<TelephoneDTO> dtos = Mapper
.Map<IList<Telephone>, IList<TelephoneDTO>>(tels.OrderBy(x => x.Type))

Resources