AutoMapper: Ignore source parameter when mapping to destination class - automapper

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.

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 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".

AutoMapper bi-directional mapping between first item in collection and single property

I'm using AutoMapper 6. Consider the following classes:
public class Source
{
public FlattenableClass Flattenable { get; set; }
public List<EmailAddress> EmailAddresses { get; set; }
}
public class Destination
{
public string FlattenableProp1 { get; set; }
public string FlattenableProp2 { get; set; }
public MappedEmailAddress EmailAddress1 { get; set; }
}
where FlattenableClass has properties named Prop1 and Prop2. As you can see, the source has a collection of EmailAddress but the destination only needs the first one because although our database allows a collection of email addresses, the application is going to support one. I believe I can arrange this mapping from Source to Destination like so:
CreateMap<Source, Destination>()
.ForMember(d => d.EmailAddress1, opt => opt.ResolveUsing(s => s.EmailAddresses?.FirstOrDefault()));
but, unsurprisingly, if I then call ReverseMap() on that, it doesn't know how to map EmailAddress1 back into the EmailAddresses collection. Is there any way to get it to map EmailAddress1 back to an EmailAddress and add it to the EmailAddresses collection?
I haven't found any support in AutoMapper but I found a work around. I just created a target property in the source to do the work for me:
public class Destination
{
public string FlattenableProp1 { get; set; }
public string FlattenableProp2 { get; set; }
public MappedEmailAddress EmailAddress1 { get; set; }
public List<MappedEmailAddress> EmailAddresses
{
get
{
List<MappedEmailAddress> emails = new List<MappedEmailAddress>();
if (EmailAddress1 != null) emails.Add(EmailAddress1);
return emails;
}
set
{
if (value == null || value.Count == 0)
EmailAddress1 = new MappedEmailAddress();
else
EmailAddress1 = value[0];
}
}
}
This lets AutoMapper map Source.EmailAddresses to Destination.EmailAddresses and then Destination.EmailAddresses does the work of mapping to EmailAddress1. It's not ideal in that I had to expose collection of email addresses property which I don't want to ever be used by anyone but auto-mapper, but it gets it done.

Pass different AutoMapper context per nested mapping

I know we can set the Context items when we call Map(), and it will be available to every map operation. Is there a way to change those context items during mapping?
Suppose I have these source types:
public class OuterSource {
public string TimeZone { get; set; }
public string Name { get; set; }
public InnerSource[] InnerArray { get; set; }
}
public class InnerSource {
public DateTime Created { get; set; }
public string Message { get; set; }
}
and these destination types:
public class OuterDest {
public string Name { get; set; }
public InnerDest[] InnerArray { get; set; }
}
public class InnerDest {
public DateTime Created { get; set; }
public string Message { get; set; }
}
The only difference is that InnerSource.Created is in UTC and I want to map it to the local time zone. However the time zone is in OuterSource, not InnerSource.
Normally, I would set up my mappers like so:
CreateMap<OuterSource, OuterDest>();
CreateMap<InnerSource, InnerDest>();
But that wouldn't work because when it comes to mapping InnerSource to InnerDest it does not have access to OuterSource.TimeZone.
So I'm currently forced to set my mapping like so:
CreateMap<OuterSource, OuterDest>()
.ForMember(dest => dest.InnerArray, opt => opt.ResolveUsing(
//loop through source.InnerArray and do the datetime
//conversion manually
));
I consider that a code smell. What I would love to do is to pass the timezone to the nested mapping somehow. I would appreciate any pointers towards that direction.

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!

Resources