AutoMapper - setting destination string to null actually makes it string.Empty - c#

With the following mapping:
Mapper.CreateMap<ObjectA, ObjectB>()
.ForMember(dest => dest.SomeStringProperty, opt => opt.MapFrom(src => null))
SomeStringProperty is now empty string not null (as I would expect)
Is this a bug? How can I get it to actually be null?
I see that opt.Ignore() will make it null but I actually want to do a conditional null like the following and the above simplified bug(?) is preventing this
Mapper.CreateMap<ObjectA, ObjectB>()
.ForMember(dest => dest.SomeStringProperty, opt => opt.MapFrom(src => src.SomeOtherProp != null ? src.SomeOtherProp.Prop1 : null))

I found the setting after looking through the source code... Confirming that this is not a bug, but in fact a configurable setting.
When I configure my mappings..
Mapper.Initialize(x =>
{
x.AddProfile<UIProfile>();
x.AddProfile<InfrastructureProfile>();
x.AllowNullDestinationValues = true; // does exactly what it says (false by default)
});

you could define map for strings using
ITypeConverter<string, string>
and when you convert return null if null. I think it is by design that you get an empty string and I even find this natural and useful myself but I may of course be wrong ;)
I can provide more precise code than above upon request but reckon you know what you're doing.

Related

How can I use not exist or null in elasticsearch query (EQL) by using Nest C#?

Everyone know that Nest Elasticsearch is not easy and boring to make clarified queries for looking something. I also stumpled upon this issue . As a result I could't use 'not empty' and null in my query.
var list = client.Count<LogMessage>(s => s.Index("xxx-*").Query(q =>
!q.Term(t => t.Field(f => f.Test.Suffix("keyword")).Value(null)) &&
q.Term(t => t.Field(f => f.Environment.Suffix("keyword")).Value("yyy")) &&
q.DateRange(t => t.Field(f => f.LogDate).GreaterThan(DateTime.Now.AddMinutes(-15)))));
Null or string.Empty is not working here. How can I use NOT null or Not Empty?
I think what you are looking for is:
.Query(q => q.Bool(b => b
.Must(m => m
.Exists(e => e
.Field(f => f.Test)
)
)
))
This checks for null values.
IMO this isn't much of a NEST usability issue as it's just non-trivial to do this in Elasticsearch itself. I have had success by negating a wildcard query (.Wilcard) on that field and/or using .Exists to find documents which do not have that field because null values are not stored on a document and empty values are difficult to search for in non-keyword text fields because the analyzers will not look for blank values.
Please see this answer and the one below it which is even shorter.
https://github.com/elastic/elasticsearch/issues/7515#issuecomment-158668403

Automapper for member condition

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.

automapper conditional Custom Value Resolver

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

How to ignore all destination members, except the ones that are mapped? [duplicate]

This question already has answers here:
AutoMapper: "Ignore the rest"?
(20 answers)
Closed 6 years ago.
Is there a way to do this? We have a SummaryDto that maps from three different types, and when we create a map for each type, props that are not mapped are throwing an error. There are about 35 attributes on the summary dto. To use Ignore() option on each one is just too much trouble. Is there a global ignore? Something like
CreateMap<Source,Target>()
.IgnoreAllUnmapped();
This is working for me:
public static class MappingExpressionExtensions
{
public static IMappingExpression<TSource, TDest> IgnoreAllUnmapped<TSource, TDest>(this IMappingExpression<TSource, TDest> expression)
{
expression.ForAllMembers(opt => opt.Ignore());
return expression;
}
}
Because ForAllMembers returns void, calling ForAllMembers(o => o.Ignore()) without this extension method wouldn't work. We want to keep the mapping expression available to enable the subsequent mappings:
CreateMap<Source, Destination>()
.IgnoreAllUnmapped()
.ForMember(d => d.Text, o => o.MapFrom(s => s.Name))
.ForMember(d => d.Value, o => o.MapFrom(s => s.Id));
I struggled with this one for quite a while too, or at least a problem similar to this. I had a class with a lot of properties on it (about 30) and I only wanted to map about 4 of them. It seems crazy to add 26 ignore statements (especially when it means that future changes to the class will mean having to update them!)
I finally found that I could tell AutoMapper to ignore everything, and then explicitly add the ones that I did want.
// Create a map, store a reference to it in a local variable
var map = CreateMap<Source,Target>();
// Ignore all members
map.ForAllMembers(opt => opt.Ignore());
// Add mapping for P1
map.ForMember(dest => dest.P1, opt => opt.MapFrom( src => src.P1));
// Add other mappings...
map.ForMember(dest => dest.P2, opt => opt.MapFrom( src => src.P2));
map.ForMember(dest => dest.P3, opt => opt.MapFrom( src => src.P3));
map.ForMember(dest => dest.P4, opt => opt.MapFrom( src => src.P4));
You'd be forgiven for thinking that you could just do this (but don't because it wont compile):
// This won't compile
CreateMap<Source,Target>()
.ForAllMembers(opt => opt.Ignore())
.ForMember(dest => dest.P1, opt => opt.MapFrom( src => src.P1));
The reason why this doesn't work is that the ForAllMembers() method doesn't support the fluent style of chaining (at least in the current version 2.0).
The good news is that the non-chaining version does indeed work. The one caveat of course is that you need to explicitly tell AutoMapper which members to map. I haven't yet found an easy way to have it both ways so that you can still use the implied mappings and ignore the broken ones.
To avoid having to explicitly specify the mapped properties, you can use IgnoreAllNonExisting. It ignores any destination properties that don't have mapped source properties.
Try to use .ConvertUsing() in your case, e.g.
CreateMap<Source,Target>()
.ConvertUsing(converter=> new Target(){
P1 = converter.P1,
....
});
So, you can describe all properties what you want to have in your object, other will be ignored.
Extension method which allows fluent syntax for the ForAllMembers method:
public static IMappingExpression<TSource, TDestination> IgnoreAllMembers<TSource, TDestination>(
this IMappingExpression<TSource, TDestination> expression
)
{
expression.ForAllMembers(opt => opt.Ignore());
return expression;
}
Usage:
The call to IgnoreAllMembers must be before the call to ForMember.
CreateMap<LocationRevision, Dto.LocationAddressMap>()
.IgnoreAllMembers()
.ForMember(m => m.LocationId, opt => opt.MapFrom(src => src.Id))
;

Inheritance related question

I have a class Base. A and B extend Base. There is also a class Relationship which contains two Base objects (source, target). Is it possible to determine whether source/target is an A or B instance?
Thanks.
Christian
PS:
Here is a little add on. I am using automapper and I would like to map the type of source/target to a string called 'Type' - GetType did not work (actually it works -s ee my comments - is and as are good solutions too):
Mapper.CreateMap<Item, ItemViewModel>()
.ForMember(dest => dest.Name, opt => opt.MapFrom(src => src.ItemName == null ? "" : src.ItemName.Name))
.ForMember(dest => dest.Type, opt => opt.MapFrom(src => src.GetType().ToString()));
How can I use is/as in this scenario?
Yup:
if (source is A)
if (source is B)
etc
or:
A sourceA = source as A;
if (sourceA != null)
{
...
}
etc
See this question for more guidance - and there are plenty of other similar ones, too.
yes.
if (source is B)...
Using the is operator? :)

Categories