Flatten nested lists Automapper - automapper

Trying to map the following
public class WorkPreferance
{
public int ID { get; set; }
public string Name { get; set; }
public IEnumerable<Location> PreferedLocations { get; set; }
}
public class Location
{
public int LocationID { get; set; }
public string LocationName { get; set; }
}
to the following destination ...
public class WorkingPreferenceViewModel
{
public int ID { get; set; }
public string Name { get; set; }
public int LocationID { get; set; }
public string LocationName { get; set; }
}
but I cant wrap my head around what should be the mapping deceleration ..
so far i have this:
Mapper.Initialize(cfg =>
{
cfg.CreateMap<Location, WorkingPreferenceViewModel>()
.ForMember(d => d.LocationID, o => o.MapFrom(s => s.LocationID))
.ForMember(d => d.LocationName, o => o.MapFrom(s => s.Description))
.ForAllOtherMembers(o => o.Ignore());
cfg.CreateMap<WorkPreferance, WorkingPreferenceViewModel>()
.ForMember(d => d.LocationID, o => o.MapFrom(s => s.PreferedLocations))
.ForMember(d => d.LocationName, o => o.MapFrom(s => s.PreferedLocations))
}

I'm afraid AutoMapper cannot do this. It can map single objects to single objects, and a collection of objects to a collection of objects.
What you want to do is map a single object to a collection of another object. I can't think of how you would use AutoMapper to achieve that.
But, I must say, I don't really understand why you have defined your viewmodel that way. Why would you repeat the Id and Name so many times? In what way does your proposed structure become more easy to process?
What I would do is create a LocationViewModel, and have WorkingPreferenceViewModel contain an IEnumerable<LocationViewModel>. That way you can map it easily with AutoMapper by creating maps between the objects and their respective view models. It will neatly take care of the fact that there is a collection in there.

Related

AutoMapper different level

Trying to map from Customer to CustomerDto but having issues with that extra layer in the source (I have no control over the source so I cannot align the two unfortunately).
public class Customer
{
public string Name { get; set; }
public AddressSet AddressSet { get; set; }
}
public class AddressSet
{
public AddressSetResults[] AddressSetResults { get; set; }
}
public class AddressSetResults
{
public string Street { get; set; }
public string HouseNumber { get; set; }
}
public class CustomerDto
{
public string Name { get; set; }
public AddressDto AddressDto { get; set; }
}
public class AddressDto
{
public string Street { get; set; }
public string HouseNumber { get; set; }
}
The following does not work for the AddressDto, any idea what I'm missing?
CreateMap<Customer, CustomerDto>()
.ForMember(dest => dest.AddressDto , opt => opt.MapFrom(src => src.AddressSet.AddressSetResults))
Two things:
1) Missing map from AddressSetResults to AddressDto
In order to map inner address you need to create map for these inner types as well:
CreateMap<AddressSetResults, AddressDto>();
2) Map from an element of AddressSetResults array, not from the array itself
This method:
.ForMember(
dest => dest.AddressDto,
opt => opt.MapFrom(src => src.AddressSet.AddressSetResults))
tells AutoMapper to map to AddressDto from AddressSetResults which is an array of AddressSetResults. This is incorrect, as AutoMapper will not know how to map from array of elements to just one element. Unless you create a map for that too, which would not be a good solution.
Assuming that AddressSetResults will contain up to one address you can fix that adding just one more call FirstOrDefault() to the end of mapping expression:
.ForMember(
dest => dest.AddressDto,
opt => opt.MapFrom(src => src.AddressSet.AddressSetResults.FirstOrDefault()))
FirstOrDefault() needs System.Linq namespace.
Why not just First()? If source AddressSetResults array would contain no elements, then mapping would fail resulting in exception as no elements would be found to satisfy the First() method call. Making it resistant to no elements scenario with FirstOrDefault() is more secure solution.
Final, working configuration:
CreateMap<Customer, CustomerDto>()
.ForMember(
dest => dest.AddressDto,
opt => opt.MapFrom(src => src.AddressSet.AddressSetResults.FirstOrDefault()));
CreateMap<AddressSetResults, AddressDto>();

Automapper & EF Core: One-to-many relationship exposed by DTO

I effectively have the following entities defined:
public class Order
{
public int Id { get; set; }
public string Name { get; set; }
}
public class Part
{
public int Id { get; set; }
public string Description { get; set; }
public int? OrderId { get; set; }
public Order Order { get; set; }
}
They are configured so that the OrderId in Part is linked to the Order Id:
// Order Model Builder
var orderModelBuilder = modelBuilder.Entity<Order>()
.ToTable("Orders");
orderModelBuilder.HasKey(i => i.Id);
orderModelBuilder.HasMany<Part>()
.WithOne()
.HasForeignKey(i => i.OrderId);
// Part Model Builder
var partModelBuilder = modelBuilder.Entity<Part>()
.ToTable("Parts");
partModelBuilder.HasKey(i => i.Id);
partModelBuilder.HasOne(i => i.Order)
.WithMany()
.HasForeignKey(i => i.OrderId);
Now I would like to map these to a detailed order DTO which includes a collection of parts for an order:
public class PartDto
{
public int Id { get; set; }
public string Description { get; set; }
}
public class OrderDetailsDto
{
public int Id { get; set; }
public string Name { get; set; }
public List<PartDto> Parts { get; set; }
}
Currently, I use Automapper's ProjectTo() to handle queries so I can query what I want and get a list of orders back already mapped to my desired DTO. Now, I want to add Parts to that query so I can get all of the orders back with parts in one query without having to loop through orders and individually fetch the parts for each order returned. I could easily do that if List was part of the Order entity class but adding it at this stage isn't really an option.
CreateMap<Part, PartDto>()
.ForMember(d => d.Id, a => a.MapFrom(s => s.Id))
.ForMember(d => d.Description, a => a.MapFrom(s => s.Description))
;
CreateMap<Order, OrderDetailsDto>()
.ForMember(d => d.Id, a => a.MapFrom(s => s.Id))
.ForMember(d => d.Name, a => a.MapFrom(s => s.Name))
.ForMember(d => d.Parts, a => a.MapFrom(s => ?????))
;
.
..
...
await dbContext.Set<Order>().Where(...).ProjectTo<OrderDetailsDto>(configurationProvider).ToListAsync()
I know I can get shadow variables using EF.Property(object, name) but not sure how to load a restricted collection (dbContext.Set.Where(i => i.OrderId == orderId)). Any suggestions would be greatly appreciated!
--
I'm also open to adding another Entity class if there is a way to make it exist next to the existing entity. So far, I can't find a way to do that without EF Core complaining that it can only have one entity per class (without a discriminator column which would defeat the purpose!).

AutoMapper automatically map prefixed properties

I want AutoMapper to map automatically Members like this:
class Model {
public int MDId { get; set; }
public int MDName { get; set; }
}
class ModelDto {
public int Id { get; set; }
public int Name { get; set; }
}
Here, I would do a
CreateMap<Model, ModelDTO>()
.ForMember(x => x.Id, e => e.MapFrom(x => x.MDId )
.ForMember(x => x.Id, e => e.MapFrom(x => x.MDName )
but,Most of my classes are like that,My Model have prefix in Database,
DTO haven't;And classes prefix not only 'MD', all different in my project
how could I make AutoMapper do the mapping automatically? thanks!

Automapper with different type of nested objects

I have a destination class like below
public class Order
{
public string OrderId { get; set; }
public List<Delivery> Deliveries { get; set;}
}
public class Delivery
{
public string ProductName { get; set; }
}
I've to map ProductName in above class from below source class
public class OrderDTO
{
public string OrderId { get; set; }
public List<OrderDelivery> Deliveries { get; set; }
}
public class OrderDelivery
{
public List<OrderDeliveryDetails> ProductDeliveryDetails { get; set; }
}
public class OrderDeliveryDetails
{
public string ProductName { get; set; }
}
How I can do this using Automapper.
(Note: Please Don't confuse with the List<OrderDeliveryDetails> in OrderDeliveryclass. It is because, it may have child products as well, but i need to take parent ProductNameonly)
Well if by taking the parent ProductName you mean the first, you can try something along these lines (otherwise I might've misunderstood your question and you can ignore my answer):
public class AutoMapperConfiguration : Profile
{
private readonly IConfiguration _mapper;
public AutoMapperConfiguration(IConfiguration mapper)
{
_mapper = mapper;
}
protected override void Configure()
{
_mapper.CreateMap<OrderDTO, Order>
.ForMember(d => d.OrderId, o => o.MapFrom(s => s.OrderId))
.ForMember(d => d.Deliveries, o => o.MapFrom(s => s.Deliveries));
_mapper.CreateMap<OrderDelivery, Delivery>
.ForMember(d => d.ProductName, o => o.MapFrom(s => s.ProductDeliveryDetails.First().ProductName));
}
}
and then simply
_mapper.Map(orderDto);
(this is, provided you have all the wireup needed to use AutoMapper).

Automapper newbie question regarding list property

As a new fan of AutoMapper, how would I use it to do the following:
Given the following classes, I want to create FlattenedGroup from Group where the list of item string maps to the title property of Item.
public class Group
{
public string Category { get; set; }
public IEnumerable<Item> Items { get; set; }
}
public class Item
{
public int ID { get; set; }
public string Title { get; set; }
}
public class FlattenedGroup
{
public string Category { get; set; }
public IEnumerable<string> Items { get; set; }
}
Thanks
Joseph
The other thing you can do is create a converter from Item -> string:
Mapper.CreateMap<Item, string>().ConvertUsing(item => item.Title);
Now you don't need to do anything special in your Group -> FlattenedGroup map:
Mapper.CreateMap<Group, FlattenedGroup>();
That's all you'd need there.
Give this a try, you can probably use Linq and a lambda expression to map the list of strings in FlattenedGroup with the titles in Group.
Mapper.CreateMap<Group, FlattenedGroup>()
.ForMember(f => f.Category, opt => opt.MapFrom(g => g.Category))
.ForMember(f => f.Items, opt => opt.MapFrom(g => g.Items.Select(d => d.Title).ToList()));
Make sure you add System.Linq to your using statements

Resources