Automapper complex objects - c#

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.

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

How can i map a child object with AutoMapper?

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

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.

Mapping DTO back to model with AutoMapper

I have a Fixture model :
public partial class Fixture
{
public int FixtureId { get; set; }
public string Season { get; set; }
public byte Week { get; set; }
//foreign key
public int AwayTeamId { get; set; }
//navigation properties
public virtual Team AwayTeam { get; set; }
//foreign key
public int HomeTeamId { get; set; }
//navigation properties
public virtual Team HomeTeam { get; set; }
public byte? AwayTeamScore { get; set; }
public byte? HomeTeamScore { get; set; }
}
And a Fixture DTO :
public class FixtureDTO
{
public int Id { get; set; }
public string Season { get; set; }
public byte Week { get; set; }
public string AwayTeamName { get; set; }
public string HomeTeamName { get; set; }
public byte? AwayTeamScore { get; set; }
public byte? HomeTeamScore { get; set; }
}
I am using AutoMapper for the mapping and this is my first attempt using it. Here is my mapping :
CreateMap<Fixture, FixtureDTO>()
.ForMember(dest => dest.Id, opt => opt.MapFrom(src => src.FixtureId))
.ForMember(dest => dest.AwayTeamName, opt => opt.MapFrom(src => src.AwayTeam.TeamName))
.ForMember(dest => dest.HomeTeamName, opt => opt.MapFrom(src => src.HomeTeam.TeamName));
CreateMap<FixtureDTO, Fixture>();
It works fine in taking the Fixture and mapping it to the FixtureDTO which I use to display the data. But when I want to update the data and pass the FixtureDTO back to map it back to Fixture I get an error.
public HttpResponseMessage PutFixture(int id, FixtureDTO fixture)
{
if (ModelState.IsValid && id == fixture.Id)
{
//do mapping manually here?
var updated = _repository.UpdateFixture(Mapper.Map<Fixture>(fixture));
return Request.CreateResponse(updated ? HttpStatusCode.OK : HttpStatusCode.NotFound);
}
return Request.CreateResponse(HttpStatusCode.BadRequest);
}
This is the error I get :
Store update, insert, or delete statement affected an unexpected number of rows (0). Entities may have been modified or deleted since entities were loaded. Refresh ObjectStateManager entries.
Can anybody help with this?
EDIT : Reverse mapping :
CreateMap<Fixture, FixtureDTO>()
.ForMember(dest => dest.Id, opt => opt.MapFrom(src => src.FixtureId))
.ForMember(dest => dest.AwayTeamName, opt => opt.MapFrom(src => src.AwayTeam.TeamName))
.ForMember(dest => dest.HomeTeamName, opt => opt.MapFrom(src => src.HomeTeam.TeamName));
CreateMap<FixtureDTO, Fixture>()
.ForMember(dest => dest.FixtureId, opt => opt.MapFrom(src => src.Id))
.ForMember(dest => dest.AwayTeam.TeamName, opt => opt.MapFrom(src => src.AwayTeamName))
.ForMember(dest => dest.HomeTeam.TeamName, opt => opt.MapFrom(src => src.HomeTeamName));
Extension to #lazyberezovsky's answer:
This is an (untested) example of the reverse mapping you might require:
CreateMap<FixtureDTO, Fixture>()
.ForMember(dest => dest.FixtureId,
opt => opt.MapFrom(src => src.Id))
.ForMember(dest => dest.AwayTeam,
opt => opt.MapFrom(src => new Team
{
TeamName = src.AwayTeamName
}))
.ForMember(dest => dest.HomeTeam,
opt => opt.MapFrom(src => new Team
{
TeamName = src.HomeTeamName
}));
I've used this format in the past and its worked fine, but alternatively you could also create specific mappings for team. Eg:
CreateMap<string, Team>()....
Also, I presume you are doing it, but assert that your configuration is valid. Eg:
[Test]
public void AutoMapper_Configuration_IsValid()
{
Mapper.Initialize(m => m.AddProfile<MyProfile>());
Mapper.AssertConfigurationIsValid();
}
First, you need to create reverse mapping from DTO to entity (provide custom member mappings, if needed):
Mapper.CreateMap<FixtureDTO, Fixture>();
And second - retrieve, map, and update existing entity
if (ModelState.IsValid && id == fixture.Id)
{
Fixture entity = _repository.FindById(fixture.Id);
Mapper.Map(fixture, entity); // Use this mapping method!
var updated = _repository.UpdateFixture(entity);
// etc
}
Generally that exception is thrown by EF when it detects one of the following:
Opimistic Concurrency Violation: This usually occurs when the entity you are trying to edit was modified else where during the time you loaded, edited and saved it. (see: Entity Framework: "Store update, insert, or delete statement affected an unexpected number of rows (0).")
An Incorrectly set ID: No ID set for Pks or FKs. I've also seen an exception like this when I mistakenly set an Entity's FK to a value and set the associated object to an object with a different ID.
Most likely this exception is being thrown because of some code in your repository. If you post the code in your repository we might get a better idea of what is causing the exception.

Categories

Resources