AutoMapper's Ignore() not working when using ForSourceMember? - c#

I'm trying to ignore a property from source type. I have defined mapping like this:
var map = AutoMapper.Mapper.CreateMap<Article, IArticle>();
map.ForSourceMember(s => s.DateCreated, opt => opt.Ignore());
map.ForSourceMember(s => s.DateUpdated, opt => opt.Ignore());
When I call Map function,
AutoMapper.Mapper.Map(article, articlePoco);
destination's properties gets updated anyway. I'm using the latest stable version downloaded from NuGet.
Any ideas why this isn't working ?
I have found similar question to this one but there is no answer attached. [question]:AutoMapper's Ignore() not working?

If the property that you want to ignore only exists in the source object then you can you MemberList.Source in combination with the option method DoNotValidate(). See below:
CreateMap<IArticle, Article>(MemberList.Source)
map.ForSourceMember(src => src.DateCreated, opt=> opt.DoNotValidate());
map.ForSourceMember(src => src.DateUpdated, opt => opt.DoNotValidate());
This is perfect if you are using AssertConfigurationIsValid and want to ignore validation of certain source properties.

Change the mapping to use ForMember:
map.ForMember(s => s.DateCreated, opt => opt.Ignore());
map.ForMember(s => s.DateUpdated, opt => opt.Ignore());

Related

Map a member of a member to the current class

I have an EF model with Vehicle.Applications.Warranties, and I'm mapping to VehicleDto. I want the Warranties in VehicleDto and I have a WarrantyDto to receive it. I keep getting null for Warranties, though.
I'm using a projection which might be wrong, but from the AutoMapper docs, I can't see any other way to access IQueryable interfaces like this.
This is what I've tried. Doesn't seem to work.
CreateMap<Vehicle, VehicleDto>();
CreateMap<Warranty, WarrantyDto>();
CreateProjection<Vehicle, VehicleDto>().ForMember(d => d.Warranties, opt => opt.MapFrom(s => s.Applications.Select(a => a.Warranties)))

Automapper: mapping RedisValue from StackExchange.Redis StreamEntry to an object strange behavior

I encountered some strange behavior while mapping StreamEntry from StackExchange.Redis library to a C# record. While I found the way to write the mapping to make it work, I still do not understand, why does it work only this way (probably due to limited knowledge of AutoMapper internals). Also, I think this could be an example to anyone who hits this problem itself.
Short introduction:
I was migrating .Net 5 project using StackExchange.Redis & AutoMapper to .Net 6, same time updating all the packages used. Automapper became version 11 and StackExchange.Redis version 2.2.88.
When I ran our unit tests, mapping validation suddenly failed. I didn't change a lot in mappings, replaced only ForAllOtherMembers with ForAllMembers and it was quite strange for me.
The problem itself:
.ForMember(dest => dest.Id, opt => opt.MapFrom(src => src.Id)) directive is invalid if src object is StreamEntry. It just throws error "Error mapping types." on validation like if there was no mapping!
Solution:
After some trying around, I've managed to find out that you should use not MapFrom<TSourceMember>(Expression<Func<TSource, TSourceMember>> mapExpression), but MapFrom<TResult>(Func<TSource, TDestination, TResult> mappingFunction) and everything works. So just changing code line above to .ForMember(dest => dest.Id, opt => opt.MapFrom((src, _) => src.Id)) magically fixed everything.
Questions:
Why? I think this is related to how RedisValue is implemented, but I really don't understand why it stopped working in .Net 6 with AutoMapper 11.
To map all members that have string type we use .ForAllMembers(opt => opt.MapFrom(src => src[opt.DestinationMember.Name])) and it still work as it was, however if you try to replace it with .ForAllMembers(opt => opt.MapFrom((src, _) => src[opt.DestinationMember.Name])), it will break.
.Net 6 console example that shows both of issues and correct way to map could be found here. TLDR: Configurations A & C are invalid and B is how it should be done.
These are usage errors. ForAllMembers, as the name indicates, will overwrite your custom config.
Just don't write code like that.

Automapper 6.2.2 conditional unflattening?

I've recently updated to the latest version of Automapper (6.2.2) to take advantage of unflattening via .ReverseMap(). Everything seemed to be going well until I realized it was always creating an empty object regardless of whether or not the flattened source properties had values. Totally understandable, but to prevent this I tried adding a condition, like so:
cfg.CreateMap<Entity, DTO>()
.ReverseMap()
.ForMember(d => d.UnflattenedType, o => o.Condition(s => s.FlattenedId.HasValue));
This doesn't seem to work though and I've been searching for a solution for too long now.
So my question is, is there a way to conditionally prevent automapper from initializing a destination object (unflattening) when using ReverseMap?
UPDATE
I've come up with a workaround by doing the following, but I'm still looking for a proper solution.
cfg.CreateMap<Entity, DTO>()
.ReverseMap()
.AfterMap((s, d) => d.UnflattenedType = s.FlattenedId.HasValue ? d.UnflattenedType : null);
According to the developers of Automapper, this is not possible as of version 6.2.2. Check out this issue I posted on their GitHub for more info:
https://github.com/AutoMapper/AutoMapper/issues/2498
Have you tried ForPath?
cfg.CreateMap<Entity, DTO>()
.ReverseMap()
.ForPath(d => d.UnflattenedType, o => o.MapFrom(s => s.FlattenedId.HasValue ? s.FlattenedId : null));

AutoMapper mapping Function to a Member's get

I have a Member named "Name" and I'm using AutoMapper to map between my ViewModel and my base Model. However, I also have a method named "GetName()" on a separate ViewModel which seems to be overriding my "Name" member's 'get' on the actual model.
I've since renamed the method to "GetFullName()" and this is no longer a problem.
This work-around works just fine, however, I would like to know what override in AutoMapper I can implement to let it know to not map function values like "GetName()" to a member's 'get'.
You can override via:
Mapper.CreateMap<Foo, FooDto>()
.ForMember(d => d.Name, opt => opt.MapFrom(src => src.Name));

AutoMapper MaxDepth() method

In my project I have a one to many relationship from Client -> Projects. As such, in one of my views, I am trying to show all of the Projects that belong to that Client. So I have an IEnumerable<ProjectDetailsViewModel> that represents all of the clients projects.
The problem is that the ProjectDetailsViewModel has a ClientDetailsViewModel which then has an IEnumerable<ProjectDetailsViewModel> and so on and so forth creating an endless loop of identical entities.
Is this where it is appropriate to use the MaxDepth() method on that .ForMember()? If so, how do I use it in this case, and if not, what is the solution?
I have tried MaxDepth(1) on the Client and whilst this prevents the StackOverflow exception, it then doesn't hold any data in the view model for that client.
The problem was that I explicitly called AutoMapper from with the AutoMapConfig as such:
.ForMember(x => x.Client, opt => opt.MapFrom(src =>
AutoMapper.Mapper.Map<ClientDetailsViewModel>(src.Client)))
If I just define it as:
.ForMember(x => x.Client, opt => opt.MapFrom(src => src.Client))
AutoMapper will know to stop after 1 recursion, and as I already have a map from Client -> ClientDetailsViewModel there is no problem.

Categories

Resources