How can we create different mapping for same entity in automapper - c#

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.

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

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

Mapping to alternate destination classes based on source

Automapper allows you to define a mapping and invoke it with the following syntax:
Mapper.CreateMap<Order, OrderDto>();
OrderDto dto = Mapper.Map<OrderDto>(order);
Is it possible to specify the destination type using a predicate of sorts?
Mapper.CreateMap<Order, FooType>().Where(s => s["_type"].ToString() == "Foo");
Mapper.CreateMap<Order, BarType>().Where(s => s["_type"].ToString() == "Bar");
Both FooType and BarType have internal properties corresponding to the values of separate keys within the source types internal dictionary.
In order to map these values correctly I need to be sure they exist, which in this case is determined by the value of the _type key.
Edit: If this is possible what would the syntax be for using Mapper.Map<>();?
You may set a map condition for each property, look on these samples:
Mapper.CreateMap<Foo,Bar>()
.ForMember(dest => dest.baz, opt => opt.Condition(src => (src.baz >= 0)));
and
Mapper.CreateMap<Source, Target>()
.ForMember(dest => dest.Value,
opt => opt.MapFrom
(src => src.Value1.StartsWith("A") ? src.Value1 : src.Value2));

Can we build search filter or other specialized view models with AutoMapper?

We have been using AutoMapper to map DTO or view models with entity (domain) models. But there are some types of view models that are built from the entity without a 1 to 1 matching.
For example, on a search page,
the options for a select control are collected from the values in the entity model.
the date range for the datepicker will be decided by the min and max dates retrieved from the entity.
some value range options are just manually created.
var model = new FilterOptionsModel
{
Organizations = all.Select(t => t.Organization.Name).Distinct(),
Locations = all.Select(t => t.Location.Name).Distinct(),
Types = all.Select(t => t.Type).Distinct(),
MinDate = all.Select(t => t.Date).Min(),
MaxDate = all.Select(t => t.Date).Max(),
NumberGroups = new[] { "All", "1 ~ 10", ">10" }
};
My question is, is it possible to create this kind of view model with AutoMapper? Or, are we asking too much for it?
Update:
Here is what I've got so far, thanks to Andrew Whitaker's answer.
Mapper.CreateMap<Tournament, FilterOptionsModel>()
.ForMember(dest => dest.Organizations,
opts => opts.MapFrom(src => src.Organization.Name.Distinct()))
.ForMember(dest => dest.Locations,
opts => opts.MapFrom(src => src.Location.Name.Distinct()));
As mentioned in the comments, I find no Select link Linq method available on src in the lambda expression.
But for MinDate and MaxDate, I still haven't figure out how to do the MapFrom. Any suggestions?
Sure it's possible:
Mapper.CreateMap<IEnumerable<MyObject>, FilterOptionsModel>()
.ForMember(
dest => dest.Organizations,
opt => opt.MapFrom(src => src.Select(t => t.OrganizationUnit.Name).Distinct())
.ForMember(
dest => dest.Locations,
opt => opt.MapFrom(src => src.Select(t => t.Location.Name).Distinct())
/* etc */
.ForMember(
dest => dest.NumberGroups,
opt => opt.UseValue(new[] { "All", "1 ~ 10", ">10" }));
Usage:
var all = /* */;
Mapper.Map<FilterOptionsViewModel>(all);

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