Automapper with EF Navigation Properties - c#

I'm trying to map two collections by using EF's navigation property.
Collection.Items is a List<Item>
CollectionDTO has a navigation property to a cross-join table called CollectionItem, which has another navigation property to Item.
I want each CollectionDTO.CollectionItem.Item to map to Collection.Item.
I have tried this but I can't figure it out.
Can someone help?
var mapperConfig = new MapperConfiguration(cfg =>
{
// CreateMap<source, destination>()
cfg.CreateMap<Collection, CollectionDTO>()
.ForMember(dest => dest.Items,
opts => opts.MapFrom(src =>
src.CollectionItems.Where(x => x.CollectionId == src.Id).ToList().ForEach(ci => ci.Item)));
});

You can use Select extension method like this:
// CreateMap<source, destination>()
cfg.CreateMap<Collection, CollectionDTO>()
.ForMember(dest => dest.Items,
opts => opts.MapFrom(src =>
src.CollectionItems.Select(ci=>ci.Item).ToList()));
If Item navigation property is a collection, then use SelectMany extension method:
// CreateMap<source, destination>()
cfg.CreateMap<Collection, CollectionDTO>()
.ForMember(dest => dest.Items,
opts => opts.MapFrom(src =>
src.CollectionItems.SelectMany(ci=>ci.Item).ToList()));

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

Mapping referenced table with C# automapper

Suppose I have a table Scopes which contains a foreign key to another table (subscopes) and I want to map it. All columns available in my Scopes table as well as some columns from my referenced table (subscopes) are required to be mapped to a DTO.
My questions are:
What should be the content of DTO?
How should I map using c# and Automapper?
this.CreateMap<tblSubScope, Sub2MainScopeDto>()
.ForMember(t => t.IdxSubScope, opt => opt.MapFrom(s => s.idxSubScope))
.ForMember(t => t.IdxMainScope, opt => opt.MapFrom(s => s.idxMainScope))
.ForMember(t => t.SubScopeDescription, opt => opt.MapFrom(s => s.strSubScope))
.ForMember(t => t.MainScopeDescription, opt => opt.MapFrom(s => s.tblMainScope.strMainScope))
.ReverseMap()
.ForMember(t => t.idxSubScope, opt => opt.MapFrom(s => s.IdxSubScope))
.ForMember(t => t.strSubScope, opt => opt.MapFrom(s => s.SubScopeDescription))
.ForMember(t => t.idxMainScope, opt => opt.MapFrom(s => s.IdxMainScope));
Problem Solved.

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

How to ReverseMap a complex object in AutoMapper

Here is the CreateMap method:
Mapper.CreateMap<Domain.Models.Organization, OrganizationInputModel>()
.ForMember(dest => dest.Address1, opts => opts.MapFrom(src => src.Address.Address1))
.ForMember(dest => dest.Address2, opts => opts.MapFrom(src => src.Address.Address2))
.ForMember(dest => dest.City, opts => opts.MapFrom(src => src.Address.City))
.ForMember(dest => dest.State, opts => opts.MapFrom(src => src.Address.State))
.ForMember(dest => dest.Zip, opts => opts.MapFrom(src => src.Address.Zip))
.ReverseMap();
Here Address is a complex object.
I was expecting to see the bi-directional model mapping. But it turns out the complex object mapping is broken:
var entity = AutoMapper.Mapper.Map<Domain.Models.Organization>(model);
I got an error:
{"message":"Null value for non-nullable member. Member: 'Address'."}
Some SO posts says reverse mapping is only for simple object, and we will have to create two Mapper.CreateMap in this case. Is it really so?
Yes, you need to create the reverse mapping in this case. The reason for this is because AutoMapper wouldn't know how to instantiate the target Address property.

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