Mapping from a Resolved Member in Automapper - 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!

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 assigning the wrong value using UseValue

I have the following code snippet:
Mapper.CreateMap<WorkOrderServiceTypeViewModel, WorkOrderServiceType>()
.ForMember(x => x.CompanyId, opt => opt.UseValue(_companyId));
Mapper.Map(model, workOrderServiceType);
When I run this, my watch shows that _companyId is 16, but after running the Mapper.Map, workOrderServiceType.CompanyId is 11.
Am I doing something wrong here?
ETA: It appears that the .UseValue only gets executed once. Any ideas why?
For reference, here are my 2 models:
public class WorkOrderServiceTypeViewModel
{
public long Id { get; set; }
public string Name { get; set; }
public bool Residential { get; set; }
public bool Commercial { get; set; }
public bool Restoration { get; set; }
public bool Cleaning { get; set; }
public bool Storage { get; set; }
}
and the database model:
If you want runtime values to be used on a mapping, you need to use the runtime values support in AutoMapper:
Mapper.CreateMap<WorkOrderServiceTypeViewModel, WorkOrderServiceType>()
.ForMember(x => x.CompanyId, opt => opt.ResolveUsing(res => res.Context.Options.Items["CompanyId"]));
Then in your mapping code:
Mapper.Map(model, workOrderServiceType, opt => opt.Items["CompanyId"] = _companyId);
Your configuration should be static and executed once - AutoMapper assumes this. So to pass runtime values into mapping, I expose a dictionary you can stuff any value in to be used inside your mapping configuration.
Is _companyId a local private variable? It doesn't look like it is a member of WorkOrderServiceTypeViewModel, which you are mapping from? If so, why use AutoMapper at all and not just a straight assignment? (Sorry if I am not understanding where _companyId is coming from)

What expression is needed for mapping this property?

I am trying to map two objects that are mostly similar with AutoMapper but one member (AudioSummary) raises the following exception :
The following property on EchoNestModel.AudioSummary cannot be mapped: AudioSummary
Add a custom mapping expression, ignore, add a custom resolver, or modify the destination type EchoNestModel.AudioSummary.
Context:
- Mapping to property AudioSummary from EchoNest.Api.AudioSummary to EchoNestModel.AudioSummary
- Mapping from type EchoNest.Api.TrackProfile to EchoNestModel.Profile
Exception of type 'AutoMapper.AutoMapperConfigurationException' was thrown.
Mapping definition
var map = Mapper.CreateMap<TrackProfile, Profile>();
map.ForMember(dest => dest.ForeignIds, opt => opt.ResolveUsing<ForeignIdResolver>());
map.ForMember(dest => dest.ForeignReleaseIds, opt => opt.ResolveUsing<ForeignReleaseIdResolver>());
map.ForMember(s => s.Media, t => t.Ignore());
map.ForMember(s => s.ProfileId, t => t.Ignore());
map.ForMember(s => s.AudioSummary, t => t.MapFrom(s => s.AudioSummary));
I've added the following two lines but a totally different error occurs :
map.ForMember(s => s.AudioSummary.Profile, t => t.Ignore());
map.ForMember(s => s.AudioSummary.AudioSummaryId, t => t.Ignore());
Expression 's => s.AudioSummary.Profile' must resolve to top-level member and not any child object's properties.
Use a custom resolver on the child type or the AfterMap option instead.
Parameter name: lambdaExpression
How can I successfully map AudioSummary ?
Source object
Target object
EDIT: In general, try AutoMapper.Mapper.AssertConfigurationIsValid();, this will show you all possible problems in your mapper setup.
From the information you provided, it looks like you need to define map for the AudioSummary classes (dest and source) as well:
[TestFixture]
public class MappingTest
{
public class SourceAudioSummary
{
public int Id { get; set; }
public string OtherData { get; set; }
}
public class TrackProfile
{
public string Whatever { get; set; }
public SourceAudioSummary AudioSummary { get; set; }
}
public class DestAudioSummary
{
public int Id { get; set; }
public string OtherData { get; set; }
}
public class Profile
{
public string Whatever { get; set; }
public DestAudioSummary AudioSummary { get; set; }
}
[Test]
public void Mapping()
{
Mapper.CreateMap<SourceAudioSummary, DestAudioSummary>();
Mapper.CreateMap<TrackProfile, Profile>();
var trackProfile = new TrackProfile
{
Whatever = "something",
AudioSummary = new SourceAudioSummary
{
Id = 1,
OtherData = "other"
}
};
var profile = Mapper.Map<TrackProfile, Profile>(trackProfile);
Assert.That(profile.Whatever == "something");
Assert.That(profile.AudioSummary.Id == 1);
Assert.That(profile.AudioSummary.OtherData == "other");
}
}

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