How can i map a child object with AutoMapper? - c#

I am not sure if i am over thinking this or not, but i cannot sus this out.
I have a parent object here called Template
public Template()
{
public long Id { get; set; }
public Scoring SubProperty { get; set; }
}
Here is my Scoring object which is a child property of Template
public enum MyEnum : short
{
Basic = 0
}
public Scoring()
{
public MyEnum Type { get; set; }
public string Text { get; set; }
}
I have a TemplateModel defined, like so, which i want to convert to
public TemplateModel()
{
public long Id { get; set; }
public string Type { get; set; }
public string Text { get; set; }
}
In my AutoMapper Profile, i have set this up like so, to covert Template to TemplateModel.
public class TemplateProfile : Profile
{
protected override void Configure()
{
// converters
this.CreateMap<TemplateType, string>().ConvertUsing(new TemplateTypeConverter());
// models
this.CreateMap<Template, TemplateModel>()
.ForMember(dest => dest.Type, opt => opt.MapFrom(src => src.Scoring.Type))
.ForMember(dest => dest.Text, opt => opt.MapFrom(src => src.Scoring.Text));
}
/// <summary>
/// Convert TemplateType to string
/// </summary>
private class TemplateTypeConverter : ITypeConverter<TemplateType, string>
{
public string Convert(ResolutionContext context)
{
return context.SourceValue.ToString().ToLower();
}
}
}
How can i convert TemplateModel back into Template?
If i add the following, i get an exception, because dest.Scoring.Type is not a root property.
this.CreateMap<TemplateModel, Template>()
.ForMember(dest => dest.Scoring.Type, opt => opt.MapFrom(src => src.Type))
.ForMember(dest => dest.Scoring.Text, opt => opt.MapFrom(src => src.Text));
Any help much appreciated. In this case a Template must always have a Scoring object, but in other cases i have optional properties. If someone could help me with both that would be great.

this.CreateMap<TemplateModel, Template>()
.ForMember(dest => dest.SubProperty, opt => opt.MapFrom(src => src));
this.CreateMap<TemplateModel, Scoring>()
.ForMember(dest => dest.Type, opt => opt.MapFrom(src => src.Type))
.ForMember(dest => dest.Text, opt => opt.MapFrom(src => src.Text));

Related

AutoMapper - Mapping child properties

I have a view model that looked like this:
class PersonSectionViewModel
{
public string FirstName { get; set; }
public string LastName { get; set; }
}
class PersonViewModel
{
public PersonSectionViewModel Main { get; set; }
public PersonSectionViewModel Partner { get; set; }
}
And I tried mapping the properties like this, but it's not working at all:
class PersonDTO
{
public string FirstName { get; set; }
public string LastName { get; set; }
public string FirstName_2 { get; set; }
public string LastName_2 { get; set; }
}
CreateMap<PersonDTO, PersonViewModel>()
.ForMember(dest => dest.Main.FirstName,
opt => opt.MapFrom(src => src.FirstName))
.ForMember(dest => dest.Main.LastName,
opt => opt.MapFrom(src => src.LastName))
.ForMember(dest => dest.Partner.FirstName,
opt => opt.MapFrom(src => src.FirstName_2))
.ForMember(dest => dest.Partner.LastName,
opt => opt.MapFrom(src => src.LastName_2))
What's the right way of mapping this?
Thank you.
Working with .ForPath().
CreateMap<PersonDTO, PersonViewModel>()
.ForPath(dest => dest.Main.FirstName,
opt => opt.MapFrom(src => src.FirstName))
.ForPath(dest => dest.Main.LastName,
opt => opt.MapFrom(src => src.LastName))
.ForPath(dest => dest.Partner.FirstName,
opt => opt.MapFrom(src => src.FirstName_2))
.ForPath(dest => dest.Partner.LastName,
opt => opt.MapFrom(src => src.LastName_2));
Demo # .NET Fiddle
Reference
Use of .ForPath() | GitHub

Stackoverflow exception automapper

So a little insight into my problem, i have 4 classes employee, employeedto, request and requestdto as shown below, they have circular references, i have created mapping configuration for them but when i try to map list of employees to employeedtos i get stackoverflow exception even though i am using MaxDepth(1).
public class Employee
{
public string Name { get; set; }
public List<Request> Requests { get; set; }
}
public class EmployeeDto
{
public string Name { get; set; }
public List<RequestDto> RequestsDto { get; set; }
}
public class Request
{
public int Id { get; set; }
public Employee CreatedBy { get; set; }
}
public class RequestDto
{
public int Id { get; set; }
public EmployeeDto CreatedBy { get; set; }
}
The mapping configuration:
cfg.CreateMap<Employee, EmployeeDto>().MaxDepth(1)
.ForMember(dest => dest.Name, opt => opt.MapFrom(src => src.Name))
.AfterMap((src, dest, context) =>
{
dest.RequestsDto = context.Mapper.Map<List<RequestDto>>(src.Requests);
});
cfg.CreateMap<EmployeeDto, Employee>().MaxDepth(1)
.ForMember(dest => dest.Name, opt => opt.MapFrom(src => src.Name))
.AfterMap((src, dest, context) =>
{
dest.Requests = context.Mapper.Map<List<Request>>(src.RequestsDto);
});
cfg.CreateMap<Request, RequestDto>().MaxDepth(1)
.ForMember(dest => dest.Id, opt => opt.MapFrom(src => src.Id))
.AfterMap((src, dest, context) =>
{
dest.CreatedBy = context.Mapper.Map<EmployeeDto>(src.CreatedBy);
});
cfg.CreateMap<RequestDto, Request>().MaxDepth(1)
.ForMember(dest => dest.Id, opt => opt.MapFrom(src => src.Id))
.AfterMap((src, dest, context) =>
{
dest.CreatedBy = context.Mapper.Map<Employee>(src.CreatedBy);
});
The exception occurs on this line when i try to map employees to employedtos.
var mapped = _mapper.Map<List<EmployeeDto>>(employees);

Automapper - ReverseMap() does not perform mapping

I have the following 2 classes:
public class ReferenceEngine
{
public Guid ReferenceEngineId { get; set; }
public string Description { get; set; }
public int Horsepower { get; set; }
}
public class Engine
{
public Guid Id { get; set; }
public string Description { get; set; }
public int Power { get; set; }
}
I am using automapper to perform a mapping from ReferenceEngine to Engine and vice versa. Notice that the properties ReferenceEngineId/Id and Horsepower/Power does not have the same name.
The following mapping configuration works and the properties having different names are successfully mapped:
public static void ConfigureMapperWorking()
{
AutoMapper.Mapper.CreateMap<ReferenceEngine, Engine>()
.ForMember(dest => dest.Description, opt => opt.MapFrom(src => src.Description)).ReverseMap();
AutoMapper.Mapper.CreateMap<ReferenceEngine, Engine>()
.ForMember(dest => dest.Id, opt => opt.MapFrom(src => Guid.Parse(src.ReferenceEngineId.ToString())))
.ForMember(dest => dest.Power, opt => opt.MapFrom(src => src.Horsepower));
AutoMapper.Mapper.CreateMap<Engine, ReferenceEngine>()
.ForMember(dest => dest.ReferenceEngineId, opt => opt.MapFrom(src => Guid.Parse(src.Id.ToString())))
.ForMember(dest => dest.Horsepower, opt => opt.MapFrom(src => src.Power));
}
However the following does not work although I invoke the method ReverseMap() at the end:
public static void ConfigureMapperNotWorking()
{
AutoMapper.Mapper.CreateMap<ReferenceEngine, Engine>()
.ForMember(dest => dest.Id, opt => opt.MapFrom(src => src.ReferenceEngineId))
.ForMember(dest => dest.Description, opt => opt.MapFrom(src => src.Description))
.ForMember(dest => dest.Power, opt => opt.MapFrom(src => src.Horsepower)).ReverseMap();
}
My question is, when property names are different, should we manually specify the TSource->TDestination and TDestination->TSource mapping? I thought the purpose of the ReverseMap is to avoid us from manually specifying the bi-directional mapping.
ReverseMap only creates a simple reverse mapping. For example it would automatically configure
Mapper.CreateMap<Engine, ReferenceEngine>();
from
Mapper.CreateMap<ReferenceEngine, Engine>();
To get anything more complex, you have to configure it manually.

AutoMapper nested mapping with DI

Mapping of nested objects is pretty straightforward with AutoMapper as long as there is a Map definition for the nested objects as well. I am facing an issue with implementing a Map between 2 objects, one of which does not and cannot have a parameter-less constructor. Let's say the Order object is such as below. The services are injected by autofac constructor dependency injection.
public class Order
{
private readonly IOrderDetailsService _orderDetailsService;
public Order(IOrderDetailsService orderDetailsService)
{
_orderDetailsService = orderDetailsService;
}
public string Name { get; set; }
public int Id { get; set; }
[NonSerialized] private IEnumerable<OrderDetails> _details;
public IEnumerable<OrderDetails> Details
{
get
{
_details = _orderDetailsService.GetDetailsByOrderId(Id);
return _details;
}
set { _details = value; }
}
}
public class OrderDetails
{
private readonly IOrderService _orderService;
public OrderDetails(IOrderService orderService)
{
_orderService = orderService;
}
public int OrderId { get; set; }
public int DetailId { get; set; }
public string DetailInfo { get; set; }
public Order Order
{
get { return _orderService.GetOrderById(OrderId); }
}
}
And if there was a View Model such as below:
public class OrderViewModel
{
public string OrderName { get; set; }
public int OrderId { get; set; }
public IEnumerable<OrderDetailsViewModel> Details { get; set; }
}
public class OrderDetailsViewModel
{
public int DetailId { get; set; }
public string DetailInformation { get; set; }
}
How would we go about Mapping Order to OrderViewModel and vice versa?
Order to OrderViewModel and OrderDetails to OrderDetailsViewModel should be straightforward:
Mapper.CreateMap<Order, OrderViewModel>()
.ForMember(dest => dest.OrderName, opt => opt.MapFrom(src => src.Name))
.ForMember(dest => dest.OrderId, opt => opt.MapFrom(src => src.Id));
Mapper.CreateMap<OrderDetails, OrderDetailsViewModel>()
.ForMember(dest => dest.DetailInformation, opt => opt.MapFrom(src => src.DetailInfo))
The issue is mapping back to Order. Before attempting to do this, I would read Jimmy Bogard's article on two way mapping. Basically AutoMapper was created to map to DTOs from domain objects, not the other way around.
Nevertheless, if you want to map back to Order and OrderDetails, you could use AutoMapper's ability to construct types using an IoC container. This involves registering the container with AutoMapper so that it knows how to resolve types.
As Jimmy points out in the comments, you'll also need to register Order and OrderDetails. I'm not all that familiar with Autofac, but I had success with this:
var builder = new ContainerBuilder();
builder.RegisterType<OrderDetailsService>().As<IOrderDetailsService>();
builder.RegisterType<OrderService>().As<IOrderService>();
/* Register Order and OrderDetails to use themselves: */
builder.RegisterType<Order>().AsSelf();
builder.RegisterType<OrderDetails>().AsSelf();
var container = builder.Build();
/* Register the container with AutoMapper */
Mapper.Configuration.ConstructServicesUsing(container.Resolve);
Now all you need to do is use .ReverseMap and .ConstructUsingServiceLocator to let AutoMapper know to use the IoC container to create the Order and OrderDetails objects:
Mapper.CreateMap<Order, OrderViewModel>()
.ForMember(dest => dest.OrderName, opt => opt.MapFrom(src => src.Name))
.ForMember(dest => dest.OrderId, opt => opt.MapFrom(src => src.Id))
.ReverseMap()
.ConstructUsingServiceLocator()
.ForMember(dest => dest.Id, opt => opt.MapFrom(src => src.OrderId))
.ForMember(dest => dest.Name, opt => opt.MapFrom(src => src.OrderName))
.ForMember(dest => dest.Details, opt => opt.Ignore());
Mapper.CreateMap<OrderDetails, OrderDetailsViewModel>()
.ForMember(dest => dest.DetailInformation, opt => opt.MapFrom(src => src.DetailInfo))
.ReverseMap()
.ConstructUsingServiceLocator()
.ForSourceMember(dest => dest.DetailInformation, opt => opt.Ignore())
.ForMember(dest => dest.DetailInfo, opt => opt.Ignore());
I think this is a good option. Thanks to Jimmy Bogard in the comments for pointing me in the right direction.

Automapper complex objects

I've begun implementing this;
Automapper, mapping to a complex object but figured there must be a better way.
So I created this;
Mapper.CreateMap<StoreTransportWindow, CSVWindow>()
.ForMember(dest => dest.DC, opt => opt.ResolveUsing(fa => fa.DC.number))
.ForMember(dest => dest.Type, opt => opt.ResolveUsing(fa => fa.Type))
;
Mapper.CreateMap<Store, CSVStore>()
.ForMember(dest => dest.StoreName, opt => opt.ResolveUsing(fa => fa.name))
.ForMember(dest => dest.StoreNumber, opt => opt.ResolveUsing(fa => fa.number))
;
Now I'd like to use the above mappings in the primary map;
Mapper.CreateMap<Store, CSVLineObject>()
.ForMember( dest => dest.store, opt => opt.ResolveUsing(/* This is where I'd like to use the above Store to CSVStore mapping */))
;
Is this possible?
edit
public class CSVStore
{
public string StoreNumber { get; set; }
public string StoreName { get; set; }
}
public class CSVWindow
{
public string Type { get; set; }
public string DC { get; set; }
public string TPC { get; set; }
public class CSVLineObject
{
public CSVStore store { get; set; }
public List<CSVWindow> storeWindows { get; set; }
As mentioned in the comment, the initial mappings should probably be more like:
Mapper.CreateMap<StoreTransportWindow, CSVWindow>()
.ForMember(dest => dest.DC, opt => opt.MapFrom(src => src.DC.number));
// Mapping for property Type not required
Mapper.CreateMap<Store, CSVStore>()
.ForMember(dest => dest.StoreName, opt => opt.MapFrom(src => src.name))
.ForMember(dest => dest.StoreNumber, opt => opt.MapFrom(src => src.number));
Now say you have the following:
public class Source
{
public Store Store { get; set; }
}
public class Destination
{
public CSVStore Store { get; set; }
}
Then the following mapping will suffice (as you've already defined the nested mapping Store to CSVStore):
Mapper.CreateMap<Source, Destination>();
However if Destination was more like this:
public class Destination
{
public CSVStore CSVStore { get; set; }
}
Then you'll need to explicitly define the properties to be mapped:
Mapper.CreateMap<Source, Destination>()
.ForMember(dest => dest.CSVStore, opt => opt.MapFrom(src => src.Store));
(Note that the mapping from Store to CVStore is applied automatically.)
If for some reason you do need to explicitly define a nested mapping, you can do something like this:
Mapper.CreateMap<Source, Destination>()
.ForMember(dest => dest.destproperty,
opt => opt.MapFrom(
src => Mapper.Map<SrcType, DestType>(src.srcproperty));
I have needed to use that at times, but not very often as the default functionality takes care of it for you automatically.
I can provide more details if required if you can expand on your requirements.

Categories

Resources