I am using auto mapper 6.1 and I want to map some values from one object to another, but there is a condition that those values can not be null and not all object properties are supposed to be mapped if so I could easily use ForAllMembers conditions. What I am trying to do is:
config.CreateMap<ClassA, ClassB>()
.ForMember(x => x.Branch, opt => opt.Condition(src => src.Branch != null),
cd => cd.MapFrom(map => map.Branch ?? x.Branch))
Also tried
config.CreateMap<ClassA, ClassB>().ForMember(x => x.Branch, cd => {
cd.Condition(map => map.Branch != null);
cd.MapFrom(map => map.Branch);
})
In another words for every property I define in auto mapper configuration I want to check if its null, and if it is null leave value from x.
Call for such auto mapper configuration would look like:
ClassA platform = Mapper.Map<ClassA>(classB);
If I've understood correctly, it may be simpler than you think. The opt.Condition is not necessary because the condition is already being taken care of in MapFrom.
I think the following should achieve what you want: it will map Branch if it's not null. If Branch (from the source) is null, then it will set the destination to string.Empty.
config.CreateMap<ClassA, Class>()
.ForMember(x => x.Branch, cd => cd.MapFrom(map => map.Branch ?? string.Empty));
And if you need to use another property from x instead of string.Empty, then you can write:
config.CreateMap<ClassA, Class>()
.ForMember(x => x.Branch, cd => cd.MapFrom(map => map.Branch ?? x.AnotherProperty));
If you want to implement complex logic but keep the mapping neat, you can extract your logic into a separate method. For instance:
config.CreateMap<ClassA, Class>()
.ForMember(x => x.Branch, cd => cd.MapFrom(map => MyCustomMapping(map)));
private static string MyCustomMapping(ClassA source)
{
if (source.Branch == null)
{
// Do something
}
else
{
return source.Branch;
}
}
You don't need the MapFrom, but you need a PreCondition instead. See here.
Related
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?
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
Using FluentAssertions:
I'm able to exclude a single property using ShouldBeEquivalentTo.
x.ShouldBeEquivalentTo(y, opts => opts.Excluding(si => !si.PropertyInfo.CanWrite));
But, how do I exclude more then 1 property when using ShouldBeEquivalentTo() ?
You don't necessarily need a separate method. Chain multiple calls fluently like so.
x.ShouldBeEquivalentTo(y, opts => opts.Excluding(si => !si.PropertyInfo.CanWrite).Excluding(si => si.SomeOtherProperty));
You 'll have to use a Function for that instead of an Expression.
x.ShouldBeEquivalentTo(y, ExcludeProperties);
private EquivalencyAssertionOptions<xx> ExcludeProperties(EquivalencyAssertionOptions<xx> options)
{
options.Excluding(t => t.CeOperator);
options.Excluding(t => t.CeOperatorName);
options.Excluding(t => t.Status);
options.Excluding(t => t.IsOperational);
return options;
}
Is it possible to use a custom value resolver in automapper only if a certain condition is met?
In my case I only want to update the value with the custom value resolver if the destination is not null.
This is an example of my code. Basically I need to add the condition onto this. Is it possible?
Mapper.CreateMap<ResponseXml, MyModel>()
.ForMember(dest => dest.FirstName,
op => op.ResolveUsing<ResponseXmlValueResolver>()
.FromMember(x => x.data.FirstOrDefault(y => y.name == "name")))
I think Eris' solution would have work; It was just grammatical errors.
Mapper.CreateMap<ResponseXml, MyModel>()
.ForMember(dest => dest.FirstName,
op => {
op.Condition(src => src != null);
op.ResolveUsing<ResponseXmlValueResolver>();
.FromMember(x => x.data.FirstOrDefault(y => y.name == "name"));
});
Is this what you wanted?
If the destination is null, the mapping will be ignore.
If the destination is null, the mapping (with the customer resolved) will be apply.
As Conditions are evaluated after resolving member, like it's said here, none of the previous answers are correct.
You should rather use PreCondition this way:
Mapper.CreateMap<ResponseXml, MyModel>()
.ForMember(dest => dest.FirstName,
op => {
op.PreCondition(src => src != null);
op.ResolveUsing<ResponseXmlValueResolver>();
.FromMember(x => x.data.FirstOrDefault(y => y.name == "name"));
});
Will this work? (I don't have a windows box in front of me at the moment)
Mapper.CreateMap<ResponseXml, MyModel>()
.ForMember(dest => dest.FirstName,
op => op.Condition(src => src != null)
.ResolveUsing<ResponseXmlValueResolver>()
.FromMember(x => x.data.FirstOrDefault(y => y.name == "name")))
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)));