Automapper: how to reverse a null substitute? - c#

I am relatively new to Automapper and just want to make sure I am not missing a shorter way to do this. I have a field in the database that when it is null, the value in the corresponding model is set to the string literal "None". I would like to do that logic in reverse when saving back to the database using the same mapperconfiguration if possible. Here is the current code (field in question is the last ".ForMember"):
var mapperConfiguration = new MapperConfiguration(cfg => cfg.CreateMap<Location, LocationModel>()
.ForMember(f => f.Name, db => db.MapFrom(f => f.LocationName))
.ForMember(f => f.Description, db => db.MapFrom(f => f.LocationDescription))
.ForMember(f => f.ERPCode, db => db.MapFrom(f => f.WarehouseCode))
.ForMember(f => f.ERPCodeQBID, db => db.MapFrom(f => f.WarehouseCodeQBID))
.ForMember(f => f.DefaultDispatchType, opt => opt.NullSubstitute("None")).ReverseMap());
The only way I have figured out to have "None" mapped back to null is to create a second map and not bother to reverse the one above. If there is a way to achieve this, please let me know.

You can chain the .ForMember() right after .ReverseMap() using a fluent syntax, which then becomes a mapping configuration of the LocationModel to Location

Related

AutoMapper - Is it possible to access the source object in AddTransform?

I'd like to check if a property is still null after a generic ResolveUsing using a IMemberValueResolver. I tried AfterMap but it did not apply after a ResolveUsing on a collection, so I thought that AddTransform might be more appropriate.
Based on the existing AutoMapper unit tests, I can see that it is possible to override the destination property:
p.CreateMap<Source, Dest>()
.ForMember(d => d.Value, opt => opt.AddTransform(d => d + ", seriously"));
Would it be possible to get access to the source object inside AddTransform to extract a value from that object to override the destination?
This is what I'm basically trying to do:
cfg.CreateMap<Foo, Bar>()
.ForMember(d => d.Description, opts =>
{
opts.ResolveUsing<LocalizeResolver, ResourceType>(src => src.ResourceType);
opts.AddTransform(d => "whatever"); // src.Description??
})
.AfterMap((src, dst) => {
// this never works
if (string.IsNullOrWhiteSpace(dst.Description))
{
dst.Description = src.Description;
}
});

How can we create different mapping for same entity in automapper

I need to write mapping for an entity to its DTO for listing purpose and details view of an entity. But for listing I need to ignore a list property because of DTO because I don't want to load it, I have enabled Lazy loading in Entity framework. How can I create two mappings of same entity or add ignore for a property while querying data for list view.
cfg.CreateMap<page, PageViewModel>().ForMember(t => t.PageRows, opts => opts.MapFrom(s => s.page_rows)).
ForMember(t => t.PageRules, opts => opts.MapFrom(s => s.page_rules.Select(x => x.rule_id)));
cfg.CreateMap<page, PageViewModel>().ForMember(t => t.PageRows, opts => opts.Ignore()).
ForMember(t => t.PageRules, opts => opts.MapFrom(s => s.page_rules.Select(x => x.rule_id)));
You can setup a Precondition with Func<ResolutionContext, bool> and then use the Map method overloads with Action<IMappingOperationOptions> to pass the controlling parameter through Items dictionary.
For instance:
.ForMember(t => t.PageRows, opts =>
{
opts.MapFrom(s => s.page_rows);
opts.PreCondition(context => !context.Items.ContainsKey("IgnorePageRows"));
})
and then:
IEnumerable<page> source = ...;
var withPageRows = Mapper.Map<List<PageViewModel>>(source);
var noPageRows = Mapper.Map<List<PageViewModel>>(source,
opts => opts.Items.Add("IgnorePageRows", null));
Reference: Conditional mapping
You would have to create two different DTO classes to map to. If you do not want to do that, you also have another option which would be to create two marker interfaces and map to those.

Projection with Automapper

I need made a projection from database to model entity. Before get data in database and map to Entity with unity of word and reposy:
public async Task<IEnumerable<Entity>> GetProjectsSponsorByYear(Guid idUser)
{
logger.Trace("Called GetProjectsSponsorByYear method in ProjectBusiness");
var Entitys = await this.unitOfWork.Repository<Entity>().Queryable().Where(p => p.SponsorId == idUser && p.Draft == true).ToListAsync();
return listProjectDataBaseAsModel;
, after that use automapper for create a map between Entity and model:
this.CreateMap<Entity, Model>()
.ForMember(dest => dest.ID, opt => opt.MapFrom(src => src.ID))
.ForMember(dest => dest.Title, opt => opt.MapFrom(src => src.Title))
public async Task<IEnumerable<Model>> GetProjectHistoricList()
{
logger.Trace("Called GetProjectHistoricList method in ProjectService");
var allProjects = await this.projectBusiness.GetAll();
var allProjectsGrid = this.entityMapper.Map<IEnumerable<Entity>, IEnumerable<Model>>(allProjects);
return allProjectsGrid;
}
But now i need made a projection from database to model entity, the problem is when use createMap, for configuration automapper, i have property with method
.ForMember(dest => dest.ImpactSupervisorDescription, opt => opt.MapFrom(src => src.ImpactSupervisor.GetDescription()))
when made the projection between repository and model with ProjectTo method a get Exection from Linq.
I know this exection is for Linq create a query and not implement this method into her.
Need to know if there is any way to achieve this, either before or after the projection.
Check out the AutoMapper.EF6 project, it uses another project, DelegateDecompiler, to build out projections based on the method and property contents. It examines the IL and builds an expression to match that IL to then pass to your queryable provider:
https://www.nuget.org/packages/AutoMapper.EF6/
Then you can project straight from the queryable to list:
var allProjectsGrid = await this.projectBusiness.GetAll()
.ProjectToListAsync<Model>();
And as a side note, you don't need those ForMember calls for ID and Title, that's the Auto part of AutoMapper :)

Sending additional data to Automapper

I'd like to send some additional information to Automapper so I can use them in the CreateMap. It seems that I have to use MappingOperatingOption and Items.
So my call is like this:
var obj = Mapper.Map<class>(x, o => o.Items.Add("data", 23));
The problem is that I cannot access that value in the MapFrom.
Mapper.CreateMap<ClassA, ClassB>()
.ForMember(x => x.FieldA, o => o.MapFrom(d =>
//accessing item here))
There's very little documentation about Automapper and I didn't come up with anything, any guidance is welcome.
Use ResolveUsing instead of MapFrom like this:
Mapper.CreateMap<ClassA, ClassB>()
.ForMember(dst => dst.FieldA,
opt =>
opt.ResolveUsing((resolution_result, src) =>
(int)resolution_result.Context.Options.Items["data"] + src.FieldB));
I am using (int)resolution_result.Context.Options.Items["data"] + src.FieldB just as an example here. You can read any piece of data from resolution_result.Context.Options.Items and use it as you like.

AutoMapper ignore properties while mapping a list

Is it possible in AutoMapper to ignore certain properties while mapping a list?
For example, I have two classes Metadata and MetadataInput.
Both have the same fields except for destination, "MetadataInput", which has an extra field.
Mapper.CreateMap <IList<Metadata>, IList<MetadataInput>>()
I've tried to use the formember option but it generates errors, probably because the property cannot be used when you want to map a list? If yes, is there alternative solution?
As Darin Dimitrov suggested, you should not try and map lists or collections.
If you have a 1 -> 1 relationship between them all, just make a map like:
Mapper.CreateMap<Metadata, MetadataInput>().ForMember(s => s.Property, t => t.Ignore());
Then you could use your list and select it to the other list.
var metadataList = new List<Metadata>();
var meatadataInputList = metadataList.Select(p => Mapper.Map<MetadataInput>(p).ToList();
Use this mapper configuration to get the desired result:
Mapper.CreateMap<Output, Input>().ForMember(s => s.InputProperty1, t => t.Ignore());
Mapper.CreateMap<Output, Input>().ForMember(s => s.InputProperty2, t => t.Ignore());
Mapper.CreateMap<Input, Output>();
listOfItems = Mapper.Map<List<Input>, List<Output>>(InputListObject);
As others suggested, we should avoid mapping lists and collections. In order to ignore all the unmapped properties, the following worked out for me.
CreateMap<Metadata, MetadataInput>()
.ForMember(dest => dest.Id, o => o.MapFrom(src => src.sourceIdentifier))
.ForMember(dest => dest.Name, o => o.MapFrom(src => src.sourceName))
.ForAllOtherMembers(opts => opts.Ignore());
ForAllOtherMembers should be the last in the method chain.
Thanks for the useful comments.
Because both lists are already made before mapping I've done it this way:
Gets the list from the db:
List<Metadata> metadatas = _Metadataservice.GetList(_Collectionservice.Get("Koekelare").ID).ToList();
Create the mapper (thanks for pointing out my mistake):
Mapper.CreateMap<Metadata, MetadataInput>().ForMember(s => s.Property, t => t.Ignore());
Make a new list and map the new (single) values one by one:
List<MetadataInput> meta = new List<MetadataInput>();
for (int i = 0; i < e.Count; i++)
{
meta.Add(Mapper.Map<Metadata, MetadataInput>(metadatas[i], input.Metadatas[i]));
}
Could someone confirm this is a good way?

Categories

Resources