Extract AutoMapper source name from custom maps via ForMember - c#

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;
}

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

C# AutoMapper: set destination value by source value after validation in conditional mapping

I'm fairly new to AutoMapper and want to know how to set a destination member to a value based on a DIFFERENT source property value and if that value is null I just want to apply the default behaviour of Automapper (keep destination value when the source is null)
CreateMap<ClassA, ClassA>()
.ForMember(dest => dest.PropertyA, opt =>
opt.MapFrom(src => src.PropertyB!= null ? null : opt.UseDestinationValue())
)
This doesn't work (don't compile) the opt.UseDestinationValue() , what option can I use here?
Please help
Try setting a precondition for mapping destination property.
CreateMap<ClassA, ClassA>().ForMember(dest => dest.PropertyA, opt => opt.PreCondition((src, dest) => src.PropertyB != null));
This will map PropertyA only when PropertyB is not null. I did try a quick sample which gave the desired result.
I think you can use the PreCondition option For Mapping Property
CreateMap<ClassA, ClassA>()
.ForMember(dest => dest.PropertyA, opt => {
opt.PreCondition(src => src.PropertyB!= null);
opt.MapFrom(src => src.PropertyB);
});
Hope to help you
You can do as follows:
var configuration = new MapperConfiguration(cfg => {
cfg.CreateMap<ClassA,ClassA>()
.ForMember(dest => dest.PropertyA, opt => opt.Condition(src => (src.PropertyB!= null)));
});
Or as follows:
var configuration = new MapperConfiguration(cfg => {
cfg.CreateMap<ClassA,ClassA>()
.ForMember(dest => dest.PropertyA, opt => {
opt.PreCondition(src => (src.PropertyB!=null));
opt.MapFrom(src => src.PropertyB); // mapping process takes place here
});
});
But the difference is that, the later runs sooner in the mapping process.
There is an excellent documentation in setting conditions for automapper:
https://docs.automapper.org/en/stable/Conditional-mapping.html

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

Complex Object mapping using Automapper

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

Replace Mapping with ConstructUsing

I make a mapping using Automapper 3.2.1:
Mapper.CreateMap<AvvisoPec, EsitiPostalizzazione>()
.ForMember(dst => dst.IDAvviso, src => src.MapFrom(v => EstraiIdAvviso(v)))
.ForMember(dst => dst.CodiceErrorePiattaforma, src => src.MapFrom(v => EstraiCodiceErrorePiattaforma(v.History)))
.ForMember(dst => dst.DescrizioneErrorePiattaforma, src => src.MapFrom(v => EstraiDescrizioneErrorePiattaforma(v.History)))
.ForMember(dst => dst.CodiceEsitoPostalizzazione, src => src.MapFrom(v => EstraiEsitoPostalizzazione(v.History)))
And this works good. now I want to remove parameterless construction in order to use a better encapsulation::
public EsitiPostalizzazione(int IDAvviso, int CodiceEsitoPostalizzazione, String CodiceErrorePiattaforma, String DescrizioneErrorePiattaforma)
{
this.IDAvviso = IDAvviso;
this.CodiceEsitoPostalizzazione = CodiceEsitoPostalizzazione;
this.CodiceErrorePiattaforma = CodiceErrorePiattaforma;
this.DescrizioneErrorePiattaforma = DescrizioneErrorePiattaforma;
}
so as far as I documented I should use the ConstructUsingafter the createMap() but I don't know how to use it, can someone help me?
All the example on the web will make something like:
Mapper.CreateMap<AvvisoPec, EsitiPostalizzazione>()
.ConstructUsing(x => new EsitiPostalizzazione(x.IdAvviso, ...))
but I have nothing like x.IDAvviso
The x in the ConstructUsing lambda should be your source type, which is AvvisoPec. So when you're using the EsitiPostalizzazione constructor you need to provide the values the way you were doing when using the MapFrom syntax.
Based on your code I would expect it to be:
Mapper.CreateMap<AvvisoPec, EsitiPostalizzazione>()
.ConstructUsing(v => new EsitiPostalizzazione(EstraiIdAvviso(v),
EstraiEsitoPostalizzazione(v.History),
EstraiCodiceErrorePiattaforma(v.History),
EstraiDescrizioneErrorePiattaforma(v.History)));

Categories