Automapper: Conditionally changing value of item in list - c#

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

Related

C# Automapper conditional mapping of child properties

I know, there are tons of questions related to this topic, but I couldn't find an answer to what I was looking for. All the questions seem to be related to the top level properties, and what I'm looking for is a conditional mapping for child properties.
In the official documentation itself, there's no such example either, unfortunately.
Here's an example that they have:
var configuration = new MapperConfiguration(cfg => {
cfg.CreateMap<Foo,Bar>()
.ForMember(dest => dest.baz, opt => opt.Condition(src => (src.baz >= 0)));
});
In my case, I want something like this:
var configuration = new MapperConfiguration(cfg => {
cfg.CreateMap<Foo,Bar>()
.ForMember(dest => dest.baz.foo, opt => opt.Condition(src => (src.baz.foo >= 0)));
});
But this is ofc throwing an exception, because you can't use "ForMember" for child properties. Instead you need to use "ForPath". What I'd expect, is to have similar code, that will work just fine for the child properties the following way:
var configuration = new MapperConfiguration(cfg => {
cfg.CreateMap<Foo,Bar>()
.ForPath(dest => dest.baz.foo, opt => opt.Condition(src => (src.baz.foo >= 0)));
});
But this code won't even compile, since the signature differes for the "Condition" method in "ForPath". So I've adjusted it to based on what the signature wanted and added "Source", before accessing the property, the following way:
var configuration = new MapperConfiguration(cfg => {
cfg.CreateMap<Foo,Bar>()
.ForPath(dest => dest.baz.foo, opt => opt.Condition(src => (src.Source.baz.foo >= 0)));
});
And even though the code complies and looks okay to me, the conditional mapping does not work. It's being ignored and the property is being set all the time, no matter what the value of "foo" is.
I have ended up with a working code, though. I took an alternative path to get what I need, using "AfterMap" method:
var configuration = new MapperConfiguration(cfg => {
cfg.CreateMap<Foo,Bar>()
.ForPath(dest => dest.baz, opt => opt.MapFrom(src => (src.Source.baz)))
.AfterMap((dest, src) =>
{
// Resetting the value of the property I don't need based on the reversed condition
if (src.baz.foo < 0)
{
src.baz.foo = default;
}
}
});
Even though I got it working, I want to know how to achieve the same result with conditional mapping? And what am I doing wrong?

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

Automapper Member mapping on codition SeemsConvoluted

I'm trying to find the correct way to implement this.
return isOptionTrue? Mapper.Map<Context>(myObject) : Mapper.Map<ReplayContext>(myObject);
Context has all the fields I actually need.
public class ContextReplay: Context
{
//This class is a work around to trick Automapper
}
Then of course the mappings
Mapper.CreateMap<myObject, Context>()
.ForMember(x => x.Id, opt => opt.MapFrom(y => y.Id))
.ForMember(x => x.Name, opt => opt.MapFrom(y => y.Name));
.ForMember(x => x.Important, opt => opt.MapFrom(y => y.NormalProp));
Mapper.CreateMap<myObject, ContextReplay>()
.ForMember(x => x.WellId, opt => opt.MapFrom(y => y.Id))
.ForMember(x => x.Name, opt => opt.MapFrom(y => y.Name));
.ForMember(x => x.Important, opt => opt.MapFrom(y => y.ReplayProp));
As you can see, I really want:
Mapper.CreateMap<myObject, Context>()
.ForMember(x => x.WellId, opt => opt.MapFrom(y => y.Id))
.ForMember(x => x.Name, opt => opt.MapFrom(y => y.Name));
.ForMember(x => x.Important, opt => opt.MapFrom(y => isOptionTrue? y.NormProp: y.ReplayProp));
For questions: Yes i have an empty sublcass, becuase I cont have two unique MyObject to Context mappings. This whole thing seems like a super hacky work around. I can't imagine they really don't support something like this, but im at a loss. I have no idea how to get my bool passed along with it.
Perhaps my imagination is limited, but personally I find "auto mapping" to be of limited use, except when mapping between two "identical" classes. Wouldn't it be more straight-forward, faster, and easier to maintain the following bit of code?
var context = new Context {
WellId = myObject.Id,
Name = myObject.Name,
Important = (isOptionTrue ? myObject.NormProp : myObject.ReplayProp)
};

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