AutoMapper different level - automapper

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>();

Related

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 8 mapping not working properly

I have two model classes, when I try to map different properties of different name by using Automapper ForMember method. It throws an automapper configuration validation exception on the mapping of different property.
I have tried a lot but It does not help.I do not know why It is throwing an exception when I try to map Quantity property with Quntity property. but when I put same name of the property in both the model classes then it works
Below is located all the model classes, exception and configurations regarding automapper.
Could you please help me, that how to solve problem?
public class ProductModel
{
public ProductModel()
{
Id = GuidContext.Current.NewGuid();
ProductHistory = new HashSet<ProductHistoryModel>();
}
public Guid Id { get; set; }
public string ProductCode { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public bool IsActive { get; set; }
public decimal? Price { get; set; }
public int? Quntity { get; set; }
public Guid ProductCategoryId { get; set; }
public Guid? BrandId { get; set; }
public Guid ProductAttributeId { get; set; }
public virtual BrandModel Brand { get; set; }
public virtual ProductCategoryModel ProductCategory { get; set; }
public virtual ProductAttributeModel ProductAttribute { get; set; }
public virtual ICollection<ProductHistoryModel> ProductHistory { get; set; }
}
The another class is
public class ProductModel
{
public string Name { set; get; }
//public List<string> Attributes { set; get; }
//public string Brand { get; set; }
public decimal? Price
{
get; set;
}
public int? Quantity { get; set; }
}
}
and the mapping configuration is
public class ProductModelMapConfigurator : Profile, IMapConfigurator
{
public void Configure()
{
Mapper.Initialize(cfg =>
{
CreateMap<StandardizeInventory.Models.Product.ProductModel, Models.ProductModel>()
//.ForMember(dest => dest.Brand, opt => opt.MapFrom(src => src.Brand.Name))
.ForMember(dest => dest.Name, opt => opt.MapFrom(src => src.Name))
.ForMember(dest => dest.Price, opt => opt.MapFrom(src => src.Price))
.ForMember(dest => dest.Quantity, opt => opt.MapFrom(src => src.Quntity));
//.AfterMap((src, dest) => {
// dest.Attributes = src.ProductAttribute.ProductAttributeValue.Select(x => x.Value).ToList();
//});
CreateMap<Models.ProductModel, StandardizeInventory.Models.Product.ProductModel>();
});
}
}
Below is the Exception Details
AutoMapper.AutoMapperConfigurationException:
Unmapped members were found. Review the types and members below.
Add a custom mapping expression, ignore, add a custom resolver, or modify the source/destination type
For no matching constructor, add a no-arg ctor, add optional arguments, or map all of the constructor parameters
==========================================================================================
AutoMapper created this type map for you, but your types cannot be mapped using the current configuration.
ProductModel -> ProductModel (Destination member list)
StandardizeInventory.Models.Product.ProductModel -> InventoryStoreApi.Models.ProductModel (Destination member list)
Unmapped properties:
Quantity
at AutoMapper.ConfigurationValidator.AssertConfigurationIsValid(IEnumerable`1 typeMaps) in
Any help would be appreciated. Thanks
You're using a Profile wrong, see the documentation on Profiles
Your profile should look like:
public class ProductModelMapConfigurator : Profile, IMapConfigurator
{
public ProductModelMapConfigurator()
{
CreateMap<StandardizeInventory.Models.Product.ProductModel, Models.ProductModel>()
//.ForMember(dest => dest.Brand, opt => opt.MapFrom(src => src.Brand.Name))
.ForMember(dest => dest.Quantity, opt => opt.MapFrom(src => src.Quntity));
//.AfterMap((src, dest) => {
// dest.Attributes = src.ProductAttribute.ProductAttributeValue.Select(x => x.Value).ToList();
//});
CreateMap<Models.ProductModel, StandardizeInventory.Models.Product.ProductModel>();
}
}
Get rid of that Mapper.Initialize call from inside your Profile, and change the profile to use a constructor, not whatever that Configure method is. You also don't need MapFrom when the names match, that's the "Auto" of "AutoMapper".

Flatten nested lists 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.

Mapping from a Resolved Member in Automapper

This is a reach, but I am going to ask anyway.
I'll lead with my example:
public class PatientInfoModel : IPatientInfoModel, IHaveCustomMappings
{
public string PatientId { get; set; }
public string PatientIdForView { get; set; }
public PatientEpisodeData PatientEpisode { get; set; }
public void CreateMappings(Profile configuration)
{
configuration.CreateMap<PatientInfoRawDto, PatientInfoModel>()
.ForMember(m => m.PatientIdForView, opt => opt.ResolveUsing<PatientIdResolver<PatientInfoRawDto, PatientInfoModel>>())
.ForMember(m => m.PatientId, opt => opt.MapFrom(p => p.patID))
.ForMember(m => m.PatientEpisode, opt => opt.MapFrom(p => new PatientEpisodeData
{
PatientId = p.patID,
PatientIdForView = this.PatientIdForView
}));
}
public class PatientEpisodeData
{
public int PatientId { get; set; }
public string PatientIdForView { get; set; }
}
}
As you can see, with the member PatientEpisode, I would like to map from one of the properties which has already been resolved (PatientIdForView).
As I could not figure out how to do this, I just set the property after the fact. But it would be interesting to find out if this is possible.
Note: I'm not really interested in using a custom value resolver unless you could pass the PatientIdForView property to it.
Cheers
Custom value resolvers do allow you to pass in the destination member value into it (I assume that's what the PatientIdForView property you mention is, the destination member value). If you need the source member value, you can use a member value resolver:
http://docs.automapper.org/en/stable/Custom-value-resolvers.html
You get the destination member, the source member that you specify, and the source/destination objects. Should be everything you need!

AutoMapper: Ignore source parameter when mapping to destination class

I want to use AutoMapper to map classes but also to prevent a source parameter from mapping over.
public class Dest
{
public string strInclude { get; set; }
}
public class Source
{
public string strInclude { get; set; }
public string strIgnore { get; set; }
}
Mapper.CreateMap<Dest, Source>()
//code to prevent strIgnore from mapping over to Dest
I can find plenty of examples for ignoring destination parameters ie
Mapper.CreateMap<Dest, Source>()
.ForMember(dest => dest.strIgnore, src => src.Ignore());
However, I can't seem to find the reverse case.

Resources