AutoMapper ignore properties while mapping a list - c#

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?

Related

Automapper: how to reverse a null substitute?

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

Automapper - Conditional mapping for both IQueryable extensions and in-memory

I have classes EntityA to DtoA and 2 usages of automapper in my .NET project.
First one is:
var result1 = dbContext
.Set<EntityA>()
.Where(...)
.ProjectTo<DtoA>(new { param1 = true } )
.ToList();
And the second:
var aList = dbContext
.Set<EntityA>()
.Where(...)
.ToList();
var result = Mapper
.Map<DtoA[]>(aList, options => options.Items["param1"] = true);
I want to have a reusable mapping working for both cases. This mapping has to be conditional for some fields based on param1 value.
How to implement it within single CreateMap<,>().ForMember() API ?
I didn't exactly get the logic you want, but you could put any logic into a method like this:
c.CreateMap<A, B>()
.ForMember(dest => dest.Items, opt => opt.ResolveUsing(src =>
{
if (src.Items["param1"] == true)
{
// Do whatever
}
return /*do whatever else*/;
}));
Is that what you want or did I misunderstand the question?
Edit:
I'll give it another try:
var result1 = dbContext
.Set<EntityA>()
.Where(...)
.Select(c => mapper.Map<A>(c))
.ToList();
However still not understanding completely what you want. Can you give an example with data, like which set should be converted into which?

AutoMapper Inline Ignore

I am using Auto Mapper to map source to destination object, I have configured my mapper like this:
Mapper.Initialize(cfg => {
cfg.CreateMap< SourceModel, DestModel>();
}
This source and dest object mapping is being used in many places, now in some cases, I have to ignore one of source model field, but not for all places. I could do like this:
CreateMap< SourceModel, DestModel>()
.ForMember(x => x.CreatedDateTime, opt => opt.Ignore());
But this will ignore CreatedDateTime property for all scenario, so I want to do it inline only.
Mapper.Map< DestModel>(sourceObject); //Here I want to ignore one property.
Please help me how I can achieve this.
Sounds like you need conditional mapping.
This answer on SO shows how to use it and the documentation can be found here.
example usage:
Mapper.CreateMap<SourceModel, DestModel>()
.ForMember(dest => dest.CreatedDateTime, opt => opt.Condition(source => source.Id == 0))

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.

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.

Categories

Resources