Complex Object mapping using Automapper - c#

I'm trying to use auto mapper to achieve the below code.
List<VlaListView> vlaVmList = new List<VlaListView>();
var vlaCollectionList = vlaCollection.ToList(); //database populate list
foreach (var vla in vlaCollectionList)
{
VlaListView vlaVm = new VlaListView();
vlaVm.VlaId = vla.VlaId;
vlaVm.UserName = vla.UserName;
vlaVm.DateOut = vla.DateOut.ToShortDateString();
vlaVm.ReturnDate = vla.ReturnDate.ToShortDateString();
vlaVm.Status = vla.Status;
string regnumbers = string.Empty;
foreach (var vehicle in vla.Vehicles)
{
regnumbers += vehicle.RegistrationNumber + ", ";
}
regnumbers = regnumbers.Remove(regnumbers.Length - 2);
vlaVm.RegistrationNumbers = regnumbers;
vlaVmList.Add(vlaVm);
}
I get all the values to map apart from the registration numbers which can have a list of vehicles with registration numbers. I'm trying to loop and get the values of vla.vehicles.RegistrationNumber, concatenate and save the results to my view model field. My Automapper code is below
AutoMapper.Mapper.Initialize(config =>
{
config.CreateMap<Vla, VlaListView>()
.ForMember(x => x.DateOut, opt => opt.MapFrom(src => src.DateOut.ToShortDateString()))
.ForMember(x => x.ReturnDate, opt => opt.MapFrom(src => src.ReturnDate.ToShortDateString()))
// this is the ForMember I can't work out, I can't get the values of the registration numbers
.ForMember(x => x.RegistrationNumbers, opt => opt.MapFrom(src => src.Vehicles.Select(v => string.Join(", ", v.RegistrationNumber))));
});
Any help with this would be much appreciated.

You are close, but string.Join should be outside the Select (currently it compiles only because string is IEnumerable<char>, thus hitting this overload, and the results is some strange list of strings with comma concatenated chars):
.ForMember(dest => dest.RegistrationNumbers, opt => opt.MapFrom(src =>
string.Join(", ", src.Vehicles.Select(v => v.RegistrationNumber))));

Related

Automapper: Conditionally changing value of item in list

I have a list that I get from a database, I want to map that to another class and get the result. However, one of the items of that list (ContractTitle) has its own properties (ContractTitle.Buyer or ContractTitle.Seller which are strings). For the mapped result, I want to change the value of each ContractTitle to either ContractTitle.Buyer or ContractTitle.Seller.
The example below is what I am attempting to do, I don't really know how to proceed though. I feel I would need a custom resolver to actually achieve this. Sorry, I am very new to automapper, find the documentation a bit confusing. Thanks!
IEnumerable < Document > document; //=some data from db
var condition = true
var configuration = new MapperConfiguration(cfg =>
cfg.CreateMap < Document, Contract > ()
.ForMember(dest => dest.ContractTitle, opt => opt
.MapFrom((src, dest, destMember, context) => context
.Options.Items["ContractTitle"]))
.ForMember(dest => dest.ContractTemplateId, opt => opt
.MapFrom(x => x.DocumentTemplateId))
.ForMember(dest => dest.Id, opt => opt
.MapFrom(x => x.Id)));
configuration.AssertConfigurationIsValid();
IMapper mapper = configuration.CreateMapper();
if (condition) {
return mapper.Map < IEnumerable < Contract >> (document, opt => opt
.Items["ContractTitle"] = document.ContractTitle.Buyer);
} else {
return mapper.Map < IEnumerable < Contract >> (document, opt => opt
.Items["ContractTitle"] = document.ContractTitle.Seller);
}
Instead of passing the internal data that needs to be mapped into the map operation, it would probably be a better idea to pass in the condition itself, and let the mapper pick one or the other based on it while mapping each member individually:
IEnumerable < Document > document; //=some data from db
var condition = true
var configuration = new MapperConfiguration(cfg =>
cfg.CreateMap<Document, Contract>()
.ForMember(dest => dest.ContractTitle, opt => opt
.MapFrom((src, dest, destMember, context) =>
((bool)context.Options.Items["Condition"])
? src.ContractTitle.Buyer
: src.ContractTitle.Seller))
.ForMember(dest => dest.ContractTemplateId, opt => opt
.MapFrom(x => x.DocumentTemplateId))
.ForMember(dest => dest.Id, opt => opt
.MapFrom(x => x.Id)));
configuration.AssertConfigurationIsValid();
IMapper mapper = configuration.CreateMapper();
return mapper.Map<IEnumerable<Contract>> (document, opt => opt
.Items["Condition"] = condition);

how multiple map in automapper

I recently met with an automapper. I have a question, how i can map multiple objects from my repostiory in one DTO.
public QuestionProfile()
{
CreateMap<Question, GrammarQuestionDTO>()
.ForMember(g => g.Question, q => q.MapFrom(source => source.Text)).ReverseMap();
CreateMap<List<GrammarQuestionDTO>, TestDTO>()
.ForMember(t => t.GrammarQuestion, g => g.MapFrom(source => source)).ReverseMap();
CreateMap<Question, AuditionQuestionDTO>()
.ForMember(a => a.Question, q => q.MapFrom(source => source.Text))
.ForMember(a => a.Url, q => q.MapFrom(source => source.AudioFile.Url)).ReverseMap();
CreateMap<List<AuditionQuestionDTO>, TestDTO>()
.ForMember(t => t.AuditionQuestion, a => a.MapFrom(source => source)).ReverseMap();
}
how can I map all these 4 objects?
public async Task<TestDTO> GenerateTest(LevelType level)
{
var grammarQuestions = await _questionRepository.GetGrammarQuestionAsync(level);
var auditionQuestions = await _questionRepository.GetAuditionQuestionAsync(level);
var essayTopic = await _questionRepository.GetEssayTopicAsync(level);
var speakingTopic = await _questionRepository.GetSpeakingTopicAsync(level);
var test = _mapper.Map<TestDTO>(grammarQuestions);
test = _mapper.Map(test, auditionQuestions); //there will be a conversion error
return _mapper.Map<TestDTO>(grammarQuestions);
}
You need to map it without list, just map it one to one and AutoMapper will handle the collections:
CreateMap<AuditionQuestionDTO, TestDTO>()
.ForMember(t => t.AuditionQuestion,
a => a.MapFrom(source => source.<PropertyName>)).ReverseMap();
And then on the map you should:
test = _mapper.Map<List<AuditionQuestionDTO>>(test, auditionQuestions);

Automapper - conditional mapping on destination

Mapping:
.ForMember(dest => dest.DisplayName, opt =>
{
opt.PreCondition(location => location.Parent != null);
opt.MapFrom(src => src.Name + ", " + src.Parent.Name);
})
.ForMember(dest => dest.DisplayName, opt =>
{
opt.PreCondition((src, dest, context) => dest.DisplayName == null);
opt.MapFrom(src => src.Name);
})
Expected result:
If the first condition is met don't override the mapping.
What actually happens:
The second mapping is overriding the first mapping.
How can I solve this?
It doesn't work because you are overwriting previous mapping expressions calling another ForMember() for the same member, which is your case is DisplayName. Consider such case:
.ForMember(d => d.DisplayName, o => o.MapFrom(s => "1"))
.ForMember(d => d.DisplayName, o => o.MapFrom(s => "2"))
.ForMember(d => d.DisplayName, o => o.MapFrom(s => "3"));
Which value will be mapped to DisplayName?
3
So in your case, your first conditional mapping expression is overwriten by the second one. To make it work, join the conditional mapping into one mapping expression:
.ForMember(
dest => dest.DisplayName,
opts => opts.MapFrom((src, dest) =>
{
if (src.Parent != null)
{
return string.Join(", ", src.Name, src.Parent.Name);
}
else
{
if (dest.DisplayName is null)
{
return src.Name;
}
else
{
return "Some other value when no conditions were met.";
}
}
}));
It would be a cool feature to have but I don't see it anywhere in Automapper documentation.
This should however work in your case if the logic is not more complex.
.ForMember(dest => dest.DisplayName, opt =>
{
opt.MapFrom(src => src.Name + (location.Parent != null ? ", " + src.Parent.Name : null));
})

Keep property of source object to destination property when mapping objects using AutoMapper Map function

I want to map most properties of the current object (this a FinancialBase instance) to another object (the 'destination' object, the schedule, an instance of a Schedule class). However, I need to keep a small set of the destination's properties.
I've got it working with a 'hack' where I capture the values explicitly then use these in the AfterMap function. See example code.
var id = schedule.Id;
var parentId = schedule.ParentId;
var scheduleNo = schedule.ScheduleNo;
var schName = schedule.SchName;
var config = new MapperConfiguration(
cfg => cfg.CreateMap<FinancialBase, Schedule>()
.ForMember(d => d.Id, opt => opt.Ignore())
.ForMember(d => d.ParentId, opt => opt.Ignore())
.ForMember(d => d.ScheduleNo, opt => opt.Ignore())
.ForMember(d => d.SchName, opt => opt.Ignore())
.AfterMap((s, d) => d.Id = id)
.AfterMap((s, d) => d.ParentId = parentId)
.AfterMap((s, d) => d.ScheduleNo = scheduleNo)
.AfterMap((s, d) => d.SchName = schName));
var mapper = config.CreateMapper();
schedule = mapper.Map<Schedule>(this);
I would prefer not to use the first four lines of my example but instead have them included using a conventional AutoMapper lambda expression. Possible?
I'd just use mapping to an existing object:
var existingSchedule = new Schedule()
{
Id = 12,
ParentId = 34,
ScheduleNo = 56,
SchName = "Foo",
};
var schedule = mapper.Map(this, existingSchedule);
And in the configuration leave the Ignore() lines but remove those with AfterMap() as they are no longer needed:
var config = new MapperConfiguration(
cfg => cfg.CreateMap<FinancialBase, Schedule>()
.ForMember(d => d.Id, opt => opt.Ignore())
.ForMember(d => d.ParentId, opt => opt.Ignore())
.ForMember(d => d.ScheduleNo, opt => opt.Ignore())
.ForMember(d => d.SchName, opt => opt.Ignore()));

Extract AutoMapper source name from custom maps via ForMember

I have successfully extracted simple source/destination pairs from an existing Automapper TypeMap using this code:
private MemberInfo getSource(Type destinationType, string destinationPropertyname)
{
TypeMap map = Mapper.GetAllTypeMaps()
.Where(m => m.DestinationType.Equals(destinationType))
.First();
IEnumerable<PropertyMap> properties =
map.GetPropertyMaps()
.Where(p => p.DestinationProperty
.Name
.Equals(destinationPropertyname,
StringComparison.CurrentCultureIgnoreCase));
PropertyMap sourceProperty = properties.First();
IMemberGetter mg = sourceProperty.GetSourceValueResolvers()
.Cast<IMemberGetter>()
.First();
return mg.MemberInfo;
}
However, when I add custom column mappings like this:
Mapper.CreateMap<Customer, CustomerViewModel>()
.ForMember(dest => dest.Cell, opt => opt.MapFrom(src => src.CellPhone))
.ForMember(dest => dest.Email, opt => opt.MapFrom(src => src.EmailAddress));
... the source part of the mapping is not available from GetSourceResolvers() that I can tell.
I appreciate any guidance you have.
Thank you.
-Jessy Houle
Is this along the lines of what you wanted?
var map = Mapper.FindTypeMapFor<Customer, CustomerViewModel>();
foreach( var propertMap in map.GetPropertyMaps() )
{
var dest = propertMap.DestinationProperty.MemberInfo;
var source = propertMap.SourceMember;
}

Categories