Mapping to alternate destination classes based on source - c#

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

Related

Resolve a few destination properties from one source property using AutoMapper

Is it possible to resolve a few destination properties from one source property using AutoMapper?
The obvious way to go is to do like this:
CreateMap<Source, Destination>
.ForMember(d => d.dest1, opt => opt.MapFrom(s => s.sour["dest1"]))
.ForMember(d => d.dest2, opt => opt.MapFrom(s => s.sour["dest2"]))
...
.ForMember(d => d.dest10, opt => opt.MapFrom(s => s.sour["dest10"]))
But is there maybe something like a custom resolver which would allow to write a method which would perform the assignment of multiple destination properties using one (or maybe even a few) source property (properties)?

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.

Automapper: map properties manually

I just started to use automapper to map DTOs<->Entities and it seems to be working great.
In some special cases I want to map only some properties and perform additional checks. Without automapper the code looks like this (using fasterflect's PropertyExtensions):
object target;
object source;
string[] changedPropertyNames = { };
foreach (var changedPropertyName in changedPropertyNames)
{
var newValue = source.GetPropertyValue(changedPropertyName);
target.SetPropertyValue(changedPropertyName, newValue);
}
Of course this code won't work if type conversions are required. Automapper uses built-in TypeConverters and I also created some specific TypeConverter implementations.
Now I wonder whether it is possible to map individual properties and use automapper's type conversion implementation, something like this
Mapper.Map(source, target, changedPropertyName);
Update
I think more information is necessary:
I already created some maps, e.g.
Mapper.CreateMap<CalendarEvent, CalendarEventForm>()
and I also created a map with a custom typeconverter for the nullable dateTime property in CalendarEvent, e.g.
Mapper.CreateMap<DateTimeOffset?, DateTime?>().ConvertUsing<NullableDateTimeOffsetConverter>();
I use these maps in a web api OData Controller. When posting new EntityDTOs, I use
Mapper.Map(entityDto, entity);
and save the entity to a datastore.
But if using PATCH, a Delta<TDto> entityDto is passed to my controller methods. Therefore I need to call entityDto.GetChangedPropertyNames() and update my existing persistent entity with the changed values.
Basically this is working with my simple solution, but if one of the changed properties is e.g. a DateTimeOffset? I would like to use my NullableDateTimeOffsetConverter.
If you just want to map only some select property than you have to do as below
// Create a map
var map = CreateMap<Source,Target>();
// ingnore all existing binding of property
map.ForAllMembers(opt => opt.Ignore());
// than map property as following
map.ForMember(dest => dest.prop1, opt => opt.MapFrom( src => src.prop1));
map.ForMember(dest => dest.prop2, opt => opt.MapFrom( src => src.prop2));
You can make some projection using MapFrom method - http://automapper.readthedocs.io/en/latest/Projection.html
Mapper.Map(source, target)
.ForMember(m => m.Property, opt => opt.MapFrom(src => src.ChangedProperty));
For example (reffering to AutoMapper documentation):
// Model
var calendarEvent = new CalendarEvent
{
Date = new DateTime(2008, 12, 15, 20, 30, 0),
Title = "Company Holiday Party"
};
// Configure AutoMapper
Mapper.CreateMap<CalendarEvent, CalendarEventForm>()
.ForMember(dest => dest.EventDate, opt => opt.MapFrom(src => src.Date.Date))
.ForMember(dest => dest.EventHour, opt => opt.MapFrom(src => src.Date.Hour))
.ForMember(dest => dest.EventMinute, opt => opt.MapFrom(src => src.Date.Minute));
To do this with the latest version of AutoMapper first you should map the properties that you want then ignore the rest.
CreateMap<TSource, TDestination>()
.ForMember(dest => dest.FirstName, opt => opt.MapFrom(src => src.FirstName))
.ForMember(dest => dest.FullName, opt => opt.MapFrom(src => $"{src.FirstName} {src.LastName}"))
.ForMember(dest => dest.Prop, opt => opt.MapFrom(src => src.AnotherProp))
// ...
.ForAllOtherMembers(opt => opt.Ignore()); // <=== Ignore The rest
Otherwise if you do map.ForAllMembers(opt => opt.Ignore()); first, it will ignore all mappings even the mappings after this.
If I read your question correctly, yes, as long as your destination (target) property matches your conversion.
So if I am going from a string to a bool for a Status of "A" or "I" (active/inactive), I can do something like:
.ForMember(dest => dest.Status, opt => opt.MapFrom(src => src.Status == "A"))
And then when going the other direction, convert it back:
.ForMember(dest => dest.Status, opt => opt.MapFrom(src => src.Status ? "A" : "I"))
A date example:
.ForMember(dest => dest.SomeDate, opt => opt.MapFrom(src => src.SomeDate.ToString("M/d/yyyy")));

Am I using Automapper 2.0's Include functionality correctly?

Either I'm not, or it isn't working... I have a single Source class that I want to map to multiple views that inherit from each other.
Basically the base class is the Detail, and the child class is Edit or Update which use all the same data as Detail, plus a couple other fields to manage their own lists or whatever.
Here are the maps I'm using:
Mapper.CreateMap<Ticket, Detail>()
.Include<Ticket, Update>()
.Include<Ticket, Edit>()
.ForMember(dest => dest.Priority, opt => opt.MapFrom(src => src.Priority.Code))
.ForMember(dest => dest.TicketID, opt => opt.MapFrom(src => src.ID))
.ForMember(dest => dest.Status, opt => opt.MapFrom(src => src.StatusCode))
.ForMember(dest => dest.Category, opt => opt.MapFrom(src => src.ProblemCategoryCode))
.ForMember(dest => dest.crmBusCode, opt => opt.MapFrom(src => src.Company.crmBusCode))
.ForMember(dest => dest.TeamMembers, opt => opt.MapFrom(src => src.Schedules.Where(s => s.CompleteTime == null)));
Mapper.CreateMap<Ticket, Update>()
.ForMember(m => m.Schedules, opt => opt.MapFrom(t => t.Schedules.Where(s => s.EmployeeID == Util.CurrentUserID() && s.CompleteTime == null)));
Mapper.CreateMap<Ticket, Edit>();
Then if I Mapper.Map(ticket) any of the properties that use MapFrom don't get evaluated, they just end up with the values they'd have had if there was no set mapping.
So what's wrong here?
As an alternative solution if you don't want to call Mapper.Map two times. You can move the common mappings of Detail into an extension method:
public static class MappingExtensions
{
public static IMappingExpression<Ticket, TDest> MapDetailProperties<TDest>(
this IMappingExpression<Ticket, TDest> mapBase) where TDest : Detail
{
return mapBase
.ForMember(dest => dest.Priority,
opt => opt.MapFrom(src => src.Priority.Code))
///....
.ForMember(dest => dest.TeamMembers,
opt => opt.MapFrom(src => src
.Schedules.Where(s => s.CompleteTime == null)));
}
}
And then use that extension method when registering the Ticket -> Update and Ticket -> Edit mappers:
Mapper.CreateMap<Ticket, Update>()
.MapDetailProperties()
.ForMember(m => m.Schedules, opt => opt.MapFrom(t => t.Schedules
.Where(s => s.EmployeeID == Util.CurrentUserID() &&
s.CompleteTime == null)));
Mapper.CreateMap<Ticket, Edit>()
.MapDetailProperties();
Then you can use Map normally:
Ticket ticket = new Ticket();
var edit = Mapper.Map<Ticket, Edit>(ticket);
var update = Mapper.Map<Ticket, Update>(ticket);
Am I using Automapper 2.0's Include functionality correctly?
No--When you use .Include, AutoMapper expects that the destination classes are in a similar hierarchy as the source classes (This is discussed further here). In other words, if you were mapping to different subclasses of Ticket to Detail, Update and Edit, Include would be appropriate.
This doesn't seem helpful in your case. I would recommend using the overload of .Map that takes an existing object and modifies it. That way, you only have to define a mapping for the base type:
Ticket ticket = new Ticket();
Edit edit = new Edit();
Mapper.Map<Ticket, Detail>(ticket, edit);
// Edit has now been automapped using the base mapping.
Mapper.Map<Ticket, Edit>(ticket, edit);
// The properties unique to Edit have now been mapped.

Categories

Resources