My database entities look as follows (redacted down to relevant information):
public class Task
{
public Guid? StatusId { get; set; }
public Status Status { get; set; }
public DateTime? StatusLastModified { get; set; }
}
public class Status
{
public string Name { get; set; }
// several other properties redacted
}
Tasks initially have no status (StatusLastModified is then also null), and once the first status has been assigned, tasks will always have a status (and StatusLastModified is then also not null).
For the sake of this question, you can assume that these properties will always both be null or not-null, I don't need to account for anomalies where one is null and the other isn't.
For my DTOs, I want to map this to the following structure:
public struct TaskDto
{
public TaskStatusDto? Status { get; set; }
}
public struct TaskStatusDto
{
public DateTime Timestamp { get; set; } // maps from Task.StatusLastModified
public StatusDto Status { get; set; } // maps from Task.Status
}
public struct StatusDto
{
public string Name { get; set; }
// and all the other properties from the Status entity
}
The goal here is that my TaskDto
only has an underlying TaskStatusDto
when there is an actual status, and otherwise it should be null. This is helpful as it negates the need to constantly null check both the status object and the last modified date.
However, I am struggling to configure this mapping in Automapper. I have created the mapping between Status
and StatusDto
:
configuration
.CreateMap<Status, StatusDto>();
My real mapping is more complicated but it's also proven to work so you can assume that this is a valid and working mapping.
The problem I'm faced with is how I can refer to this mapping when creating the custom mapping between Task
and TaskDto
. I've currently tried the following:
configuration
.CreateMap<Task, TaskDto>()
.ForMember(
dto => dto.Status,
opt => opt.MapFrom(entity =>
entity.Status == null
? null as TaskStatusDto?
: new TaskStatusDto()
{
TimeStamp = entity.StatusLastModified.Value,
Status = // ??? make me a StatusDto from entity.Status
}
));
The part I don't know how to fill in is the comment. I have access to the original entity (entity.Status
), but I don't know how to tell Automapper to convert this object to a StatusDto
according to a mapping that it should already know.
My approach also feels more contrived than it should be, but I don't know how to otherwise introduce this intermediary TaskStatusDto
object if it isn't backed by an actual entity in my source data.
A small footnote, unsure if relevant: I am using Heroic.Automapper, which means that the TaskDto
and StatusDto
maps are being configured in separate locations (i.e. inside the TaskDto
and StatusDto
class, respectively) and the Heroic framework will combine all these maps at runtime.
public class TaskDto : IMapFrom<Task>, IHaveCustomMappings
{
public void CreateMappings(IMapperConfigurationExpression configuration)
{
// mapping from Task to TaskDto
}
}
public class StatusDto : IMapFrom<Status>, IHaveCustomMappings
{
public void CreateMappings(IMapperConfigurationExpression configuration)
{
// mapping from Status to StatusDto
}
}
However, as far as I understand it, the setting up of the map itself isn't Heroic-specific and should be purely Automapper syntax.
I think a cleaner solution would be to create your own CustomResolver:
public class TaskStatusDtoResolver : IValueResolver<Task, TaskDto, TaskStatusDto?>
{
public TaskStatusDto? Resolve(Task source, TaskDto destination, TaskStatusDto? member, ResolutionContext context)
{
if (source.Status == null)
{
return null;
}
return new TaskStatusDto
{
Status = context.Mapper.Map<StatusDto>(source.Status),
Timestamp = source.StatusLastModified.Value
};
}
}
And you can use the following configuration:
cfg.CreateMap<Status, StatusDto>();
cfg.CreateMap<Task, TaskDto>()
.ForMember(dest => dest.Status, opt => opt.MapFrom<TaskStatusDtoResolver>());
See the working example here
Alternatively you can define all the mappings in the configuration:
var configuration = new MapperConfiguration(cfg =>
{
cfg.CreateMap<Status, StatusDto>();
cfg.CreateMap<Task, TaskDto>().ForMember(
dest => dest.Status,
src => src.MapFrom((task, taskDto, member, context) =>
{
return task.Status == null ? null as TaskStatusDto? : new TaskStatusDto()
{
Timestamp = task.StatusLastModified.Value,
Status = context.Mapper.Map<StatusDto>(task.Status)
};
}
));
});
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With