Automapper 6.2.2 conditional unflattening? - c#

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

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.

Update only mapped fields or Ignore the unmapped fields - Automapper?

I'm very new to Automapper and I'm facing difficulties in Updating mapped fields. I must agree that there are millions of answers for this question in internet but the problem is nothing helps for my scenario.
Issue:
I'm trying to update only the mapped fields. But when I do that, my complete destination properties gets updated.
For e.g.,
My destination object(DB Table) holds 5 properties and all the 5
properties accepts null. But I have only 3 properties in my
source(Model file). I'm mapping all the three source fields with the
destination fields but I don't have mapping for the remaining two
destination properties. You would have guessed it correctly, yes when
I update this, that two unmapped fields are also getting update to null(default value - DB). But it
should not be the case, rather it should have the existing values.
Kindly see my code below,
References:
Automapper Version: v6.2.2 and Runtime Version: v4.0.3
Repository.cs,
var objectToUpdate = Mapper.Map<TDomain>(entity); //TDestination Map<TDestination>(object source);
DatabaseContext.Entry(objectToUpdate).State = EntityState.Modified;
InvoiceLineMapping.cs
public static void Map(IProfileExpression profile)
{
profile.CreateMap<Registration, PT_Registration>()
.ForMember(d => d.Name_VC, map => map.MapFrom(s => s.Name))
.ForMember(d => d.UserName_VC, map => map.MapFrom(s => s.Username))
.ForMember(d => d.Mobile_VC, map => map.MapFrom(s => s.Mobile));
}
Note: As I said before, I have two unmapped fields are there in the destination and they are EMail_VC and Comments_VC and their values are sample#test.com and testComments respectively. When update happens, these two fields becomes null(default value)
I tried,
.ForAllOtherMembers(opt => opt.Ignore()); //after the last mapping i.e Mobile
profile.CreateMap(MemberList.Source) // I also tried MemberList.Destination
I also tried, AutoMapper: "Ignore the rest"? extenstion method,
I tried puttin ReverseMap() in the end. //after the last mapping i.e Mobile
I even tried to find the null value and removing from destination object(objectToUpdate - you can see above) but nothing helps.
So can someone please look into this and provide me a better solution. I knew solution would be simpler but as I'm not aware of it, it takes more and more time.
I appreciate your time. Any help would be great helpful.

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

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

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