Map a member of a member to the current class - c#

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

Related

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

Create AutoMapper mapping with calculated value?

I'm trying to configure a simple AutoMapper mapping from an Entity Framework entity to a view model object. It mostly works but in the view model I have an int field to hold a count. This field does not exist in the source entity.
cfg.CreateMap<Feed, FeedVM>()
.ForMember(dest => dest.Count, opt => opt.MapFrom(src => src.Orders.Count()));
When I check the validity of the mapping I get the following error message:
The following property on Feed cannot be mapped:
Add a custom mapping expression, ignore, add a custom resolver, or modify the destination type Feed.
Context:
Mapping from type FeedVM to Feed
If I understand the Automapper syntax correctly I am mapping from Feed to FeedVM but the error message seems to indicate that I am mapping from FeedVM to Feed.
What should I be doing to map the value 42 to the Count field in FeedVM?
You should use ResolveUsing:
cfg.CreateMap<Feed, FeedVM>()
.ForMember(dest => dest.Count, opt => opt.ResolveUsing(src => src.Orders.Count()));
Update
John indicates in the comment below that the mappings are correct, the problem lies in a mapping for another entity that is related to Feed. In that entity he is mapping both directions.

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