Map 2 parent classes using AutoMapper - automapper

I'm trying to map 2 classes inheriting different base (but with common property). When I use Map, parent properties doesn't map automatically (which I think should based on inhertiance laws). Please suggest if I'm wrong somewhere:
public class SourceBase
{
public bool IsSuccess { get; set; }
}
public class DestBase
{
public bool Success { get; set; }
}
public class ChildSource : SourceBase
{
public string SourceName { get; set; }
}
public class ChildDest : DestBase
{
public string DestName { get; set; }
}
Creating Maps
AutoMapper.Mapper.CreateMap<SourceBase, DestBase>()
.ForMember(dest => dest.Success, opt => opt.MapFrom(source => source.IsSuccess));
AutoMapper.Mapper.CreateMap<ChildSource, ChildDest>()
.ForMember(dest => dest.DestName,opt=>opt.MapFrom(source=>source.SourceName));
Using the Map
ChildSource ch = new ChildSource()
{
IsSuccess = true,
SourceName = "user1"
};
var obj = AutoMapper.Mapper.Map<ChildDest>(ch);
I expected IsSuccess as True and DestName as user1. But only SourceName gets set and IsSuccess remains false. If I use same name (IsSuccess) in both, it works which is because of automapping via name. But How can I use the existing format of different property names (but same types) in different class. I do not want to explicitly map parent properties while writing map for each child class.

You need to tell AutoMapper about the inheritance by using the Include method:
Mapper.CreateMap<SourceBase, DestBase>()
.Include<ChildSource, ChildDest>()
.ForMember(dest => dest.Success, opt => opt.MapFrom(source => source.IsSuccess));

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

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!

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: How to leverage a custom INamingConvention?

I am working with a database where the designers really seemed to enjoy capital letters and the underscore key. Since I have a simple ORM, my data models use these names as well. I need to build DTOs and I would prefer to give them standard names since we are exposing them through services.
The code below is now corrected! The test passes so use this as a reference if you need to use multiple naming conventions
using System;
using System.Collections.Generic;
using System.Text.RegularExpressions;
using AutoMapper;
using NUnit.Framework;
namespace AutomapperTest
{
public class DATAMODEL
{
public Guid ID { get; set; }
public string FIRST_NAME { get; set; }
public List<CHILD_DATAMODEL> CHILDREN { get; set; }
}
public class CHILD_DATAMODEL
{
public Guid ID { get; set; }
public int ORDER_ID { get; set; }
}
public class DataModelDto
{
public Guid Id { get; set; }
public string FirstName { get; set; }
public List<ChildDataModelDto> Children { get; set; }
}
public class ChildDataModelDto
{
public Guid Id { get; set; }
public int OrderId { get; set; }
}
public class UpperUnderscoreNamingConvention : INamingConvention
{
private readonly Regex _splittingExpression = new Regex(#"[\p{Lu}0-9]+(?=_?)");
public Regex SplittingExpression { get { return _splittingExpression; } }
public string SeparatorCharacter { get { return "_"; } }
}
public class Profile1 : Profile
{
protected override void Configure()
{
SourceMemberNamingConvention = new UpperUnderscoreNamingConvention();
DestinationMemberNamingConvention = new PascalCaseNamingConvention();
CreateMap<DATAMODEL, DataModelDto>();
CreateMap<CHILD_DATAMODEL, ChildDataModelDto>();
}
}
[TestFixture]
public class Tests
{
[Test]
public void CanMap()
{
//tell automapper to use my convention
Mapper.Initialize(x => x.AddProfile<Profile1>());
//make a dummy source object
var src = new DATAMODEL();
src.ID = Guid.NewGuid();
src.FIRST_NAME = "foobar";
src.CHILDREN = new List<CHILD_DATAMODEL>
{
new CHILD_DATAMODEL()
{
ID = Guid.NewGuid(),
ORDER_ID = 999
}
};
//map to destination
var dest = Mapper.Map<DATAMODEL, DataModelDto>(src);
Assert.AreEqual(src.ID, dest.Id);
Assert.AreEqual(src.FIRST_NAME, dest.FirstName);
Assert.AreEqual(src.CHILDREN.Count, dest.Children.Count);
Assert.AreEqual(src.CHILDREN[0].ID, dest.Children[0].Id);
Assert.AreEqual(src.CHILDREN[0].ORDER_ID, dest.Children[0].OrderId);
}
}
}
Create your mappings in profiles, and define the INamingConvention parameters as appropriate.
I don't like the global/static, so I prefer using Initialize and define all of my mappings together. This also has the added benefit of allowing a call to AssertConfiguration... which means if I've borked my mapping I'll get the exception at launch instead of whenever my code gets around to using the problematic mapping.
Mapper.Initialize(configuration =>
{
configuration.CreateProfile("Profile1", CreateProfile1);
configuration.CreateProfile("Profile2", CreateProfile2);
});
Mapper.AssertConfigurationIsValid();
in the same class with that initialization method:
public void CreateProfile1(IProfileExpression profile)
{
// this.CreateMap (not Mapper.CreateMap) statements that do the "normal" thing here
// equivalent to Mapper.CreateMap( ... ).WithProfile("Profile1");
}
public void CreateProfile2(IProfileExpression profile)
{
profile.SourceMemberNamingConvention = new PascalCaseNamingConvention();
profile.DestinationMemberNamingConvention = new LowerUnderscoreNamingConvention();
// this.CreateMap (not Mapper.CreateMap) statements that need your special conventions here
// equivalent to Mapper.CreateMap( ... ).WithProfile("Profile2");
}
if you do it this way, and don't define the same mapping in both profiles, I don't think you need anything to "fill in the blank" from the original question, it should already be setup to do the right thing.
What about
public class DATAMODELProfile : Profile
{
protected override void Configure()
{
Mapper.CreateMap<DATAMODEL, DATAMODEL>();
Mapper.CreateMap<DATAMODEL, SOMETHINGELSE>();
Mapper.CreateMap<DATAMODEL, DataModelDto>()
.ForMember(dest => dest.Id, opt => opt.MapFrom(src => src.ID))
.ForMember(dest => dest.FirstName, opt => opt.MapFrom(src => src.FIRST_NAME))
.ForMember(dest => dest.ChildDataModels, opt => opt.MapFrom(src => src.CHILD_DATAMODELS));
}
}

Automapper: CreateMap but for a property which is not initialized yet

How can I create a Map with Automapper when in the underlying destination type a property not yet has initialized?
Example:
public class UserAccount
{
public string name { get; set; }
public Dictionary<string,string> properties { get; set; }
}
public class UserAccountOtherType
{
public string name { get; set; }
public string Property1 {get;set; }
}
public static UserAccount CustomMap(UserAccountOtherType type2)
{
AutoMapper.Mapper.CreateMap<UserAccount,UserAccountOtherType>()
.ForMember(dest => dest.properties["Property1", opt => opt.MapFrom(src => (string)src.Property1));
return AutoMapper.Mapper.Map<UserAccount,UserAccountOtherType>(type2);
}
When I try to execute this code it fails because the Dictionary in UserAccount is not yet initialized. I cannot initialize the Object by myself because the UserAccount Class is a Datacontract of a WCF Serviceinterface.
I have to create a Dicationary by myself and assign it to the property.
UserAccount b = new UserAccount();
Dictionary<string,string> properties = new Dictionary<string,string>();
b.properties = properties;
How can I solve this with Automapper? Or is my approach not senseful?
Only way I can think of to do something like this is to write a class that implements AutoMapper.IValueResolver. Then opt.MapFrom... becomes opt.ResolveUsing....FromMember... Probably at that point your destination is the entire dictionary (I think if you leave out the FromMember you get the whole object into your resolver
in your implementation of IValueResolver.Resolve, try breaking into the debugger and checking out the ResolutionContext in source.Context, then once you have your dictionary built, return source.New(myDictionary)

Resources