How to do nested mapping with AutoMapper? - c#

I have an entity and model defined as this:
public class User
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public Location Location { get; set; }
public string Gender { get; set; }
public string Skills { get; set; }
public bool isPrivate { get; set; }
}
public class Location
{
public int Id { get; set; }
public string City { get; set; }
public string Country { get; set; }
}
public class UserModel
{
public string FirstName { get; set; }
public string LastName { get; set; }
public Location Location { get; set; }
public bool isPrivate { get; set; }
}
Then, I have setup a mapping profile defined as this:
public MappingProfile()
{
CreateMap<User, UserModel>()
.ReverseMap();
}
And the point is that this works to some extend, but the Location complex type is not mapped properly. I can always flatten it, by including LocationCity and LocationCountry inside the UserModel class, but that's not what I wanna do. I want Location to appear as a nested property in the returned result, as it is defined originally. How can I achieve this in AutoMapper?

You need to add a mapping between Location to Location too. Yep, the source and destination are identitic.
So your mapping profile should look like this:
public MappingProfile()
{
CreateMap<Location, Location>();
CreateMap<User, UserModel>()
.ReverseMap();
}

You could define your configuration like this:
CreateMap<User, UserModel>()
.ForMember(um => um.Location , opt => opt.MapFrom(u => u.Location))
.ReverseMap();
The ReverseMap can only create a simple mapping. For any complex type, you need to configure it manually, because AutoMaper don't know exactly how to instantiate the Location property and it will return null reference.

Related

Automapper generic source to known target resolver issue

It's been a while since i've used automapper, but i'm almost sure that my situation should be possible.
Setup
I created the following mapping configuration:
var map = cfg.CreateMap<TSource, Structure>();
So in my situation the source is a generic type (unknown) and the target type is Structure (known).
A possible option for the TSource type could be:
public class DataChannel
{
public string Id { get; set; }
public string Description { get; set; }
public string Ean { get; set; }
public DateTimeOffset ValidFrom { get; set; }
public bool IsManual { get; set; }
public string Type { get; set; }
public string Unit { get; set; }
public string Address { get; set; }
public string BuildingId { get; set; }
}
The target Structure object looks like this:
public class Structure : IStructure
{
public Structure()
{
Children = new List<Structure>();
Properties = new List<StructureProperty>();
}
public int Id { get; set; }
public ICollection<StructureProperty> Properties { get; set; }
public List<Structure> Children { get; set; }
}
Situation
For example, I would like the string properties "Unit" and "Type" to be added as a StructureProperty object to the Properties collection of the Structure entity.
map.ForMember(c => c.Properties, m => m.MapFrom<StructurePropertyResolver<TSource>>());
How can this be done?

Automapper and mapping complex collections

Hi I have a question about automapper, thing is I have a model that has nested collection of other models, and models in that collection also has a collection of models something like (DB model):
public class Cabin
{
public uint Id { get; set; }
public string Name { get; set; }
public Rack[] Racks { get; set; }
}
public class Rack
{
public uint Id { get; set; }
public string RackName { get; set; }
public IPAddress IpAddress { get; set; }
public int Port { get; set; }
public Module[] Modules { get; set; }
}
public class Module
{
public uint Id { get; set; }
public string ModuleName { get; set; }
}
Well from Dto side I have something like:
public class CabinDto
{
public uint Id { get; set; }
public string Name { get; set; }
public RackDto[] Racks { get; set; }
}
public class RackDto
{
public uint Id { get; set; }
public string Name { get; set; }
public ModuleDto[] Modules{ get; set; }
}
public class ModuleDto
{
public string Name { get; set; }
}
So I want to map it all at once, but figure out a way to map a list object with different properties names.
For main class I have:
CreateMap<Db.Cabin, Dto.Cabin>()
.ForMember(dest => dest.Id, opt => opt.MapFrom(src => src.Id))
.ForMember(dest => dest.Name, opt => opt.MapFrom(src => src.Name));
// how to map nested list
I could just add some method that assigns values and map to this method, but it does not feel right. I looked in documentation and there are only examples with simple collection with same name lists.
Is there a way to do it?
You just need to add mapping for the type in the nested list and AutoMapper will take care of it.
CreateMap<Db.Module, Dto.Module>();
CreateMap<Db.Rack, Dto.Rack>();
Also when the name of properties in source and origin are the same, you don't need to call the ForMember() method.
So in your case you need it for the ModuleName to Name of the Module to ModuleDto mapping, and same for the Rack class, see this fiddle.

How to flattening child object used repeatedly in C# with Automapper

Using the following entities
public class User
{
public Guid Id { get; set; }
public string Username { get; set; }
}
public class GeneralEntity
{
public Guid Id { get; set; }
public User CreatedByUser { get; set; }
public User DeletedByUser { get; set; }
}
How do I flatten this to the GeneralEntityDto below?
public class GeneralEntityDto
{
public Guid Id { get; set; }
public string CreatedByUsername { get; set; }
public string DeletedByUsername { get; set; }
}
I have tried setting up my mappings as seen below but it fails with a complaint about "CreatedByUsername" and "DeletedByUsername" not being mapped.
protected void Configure()
{
CreateMap<GeneralEntity, GeneralEntityDto>()
.ForMember(dest => dest.CreatedByUsername,
opt => opt.MapFrom(src => src.CreatedByUser.Username))
.ForMember(dest => dest.DeletedByUsername, opt =>
opt.MapFrom(src => src.DeletedByUser.Username));
}
You can use the naming convention that automapper provides.
Basically if you include the exact string of the property name of the source Object you do not have to add ForMember() automapper is clever enough to do it automatically.
That means for example :
public class GeneralEntity
{
public Guid Id { get; set; }
public User CreatedBy { get; set; } // renaming just for simplicity
public User DeletedBy { get; set; } // renaming just for simplicity
}
public class GeneralEntityDto
{
public Guid Id { get; set; }
public string CreatedByUsername { get; set; }
public string DeletedByUsername { get; set; }
}
Reference also to these:
http://docs.automapper.org/en/stable/Flattening.html
AutoMapper TwoWay Mapping with same Property Name

Automapper - Cannot ignore a property when mapping to a list existing entities

I have two class TenantRestrictSourceEntity, TenantRestrictSource and mapper profile
CreateMap<TenantRestrictSourceEntity, TenantRestrictSource>()
.ForMember(dest => dest.Tenant, opt => opt.Ignore());
public class TenantRestrictSourceEntity
{
public string SourceType { get; set; }
public bool? Enable { get; set; }
public int TenantId { get; set; }
}
public sealed class TenantRestrictSource
{
public int Id { get; set; }
public string SourceType { get; set; }
public bool Enable { get; set; }
public int TenantId { get; set; }
public Tenant Tenant { get; set; }
}
When I map one TenantRestrictSourceEntity to one TenantRestrictSource with Mapper.Map(tenantRestrictSourceEntity, tenantRestrictSource). Everything works fine. Tenant property was ignored correctly.
But when I try to map a list TenantRestrictSourceEntity to a list TenantRestrictSource with Mapper.Map(tenantRestrictSourceEntities, tenantRestrictSources) Tenant property is always null.
How can I ignore that property when mapping a list object?

Automapper - Unable to map nested objects/collecions

I've tried numerous examples on here and from the automapper wiki and I am still unable to get this issue resolved. I am trying to map a nested object and a nested collection and no matter what I do it always throws an error. The only way I can get the controller to return data is by turning on option.ignore for the two properties.
These are the business layer objects I am trying to map
public class LocationBL
{
public int Id { get; set; }
public string Name { get; set; }
public string Address { get; set; }
public string Address2 { get; set; }
public string City { get; set; }
public string State { get; set; }
public string Zipcode { get; set; }
public string Country { get; set; }
public DbGeography Coordinates { get; set; }
public int LocationType_Id { get; set; }
public virtual LocationTypeBL LocationType { get; set; }
public virtual ICollection<SportBL> Sports { get; set; }
}
public class LocationTypeBL
{
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<LocationBL> Locations { get; set; }
}
public class SportBL
{
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<LocationBL> Locations { get; set; }
public virtual ICollection<UserBL> Users { get; set; }
}
These are the data layer objects
public class Location : EntityData
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
[ForeignKey("Company")]
public int? CompanyId { get; set; }
[Required]
public string Name { get; set; }
public string Address { get; set; }
public string Address2 { get; set; }
public string City { get; set; }
public string State { get; set; }
public string Zipcode { get; set; }
public string Country { get; set; }
[Required]
public DbGeography Coordinates { get; set; }
[ForeignKey("LocationType")]
public int LocationType_Id { get; set; }
public virtual LocationType LocationType { get; set; }
public virtual ICollection<Sport> Sports { get; set; }
public virtual Company Company { get; set; }
}
public class LocationType : EntityData
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<Location> Locations { get; set; }
}
public class Sport : EntityData
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
[Required]
public string Name { get; set; }
public virtual ICollection<Location> Locations { get; set; }
public virtual ICollection<User> Users { get; set; }
}
This is my mapping profile
public class LocationProfile : Profile
{
public LocationProfile()
{
CreateMap<LocationType, LocationTypeBL>();
CreateMap<LocationTypeBL, LocationType>();
CreateMap<Location, LocationBL>()
.ForMember(Dest => Dest.Sports,
opt => opt.MapFrom(src => src.Sports))
.ForMember(Dest => Dest.LocationType,
opt => opt.MapFrom(src => src.LocationType));
CreateMap<LocationBL, Location>()
.ForMember(Dest => Dest.Sports,
opt => opt.MapFrom(src => src.Sports))
.ForMember(Dest => Dest.LocationType,
opt => opt.MapFrom(src => src.LocationType));
}
}
UPDATE *******
This is my LocationType profile
public class LocationTypeProfile : Profile
{
public LocationTypeProfile()
{
CreateMap<LocationType, LocationTypeBL>();
CreateMap<LocationTypeBL, LocationType>();
}
}
This is my Sport profile
public class SportProfile : Profile
{
public SportProfile()
{
CreateMap<Sport, SportBL>();
CreateMap<SportBL, Sport>();
}
}
Not sure if it matters but this is an Azure Mobile App backend using Autofac, WebAPI, and OWIN. This is my first time using AutoMapper and Autofac so please forgive me as I am still learning. The profiles are all registered and if I set the nested objects to ignore, the controller returns the proper data.
Thank you in advance!!!
You are almost there. You need to instruct AutoMapper on how to map the nested objects as well. So you need to create a map for the Sport to SportBL, and vice-versa, also.
// use ForMember if needed, but you know how to do that so I won't
// show it.
CreateMap<Sport, SportBL>();
Then AutoMapper will use that mapping when it mapping nested complex types.
Another note, if your classes have the same properties, you can just call the ReverseMap() method and it will do bidirectional mapping for you.
So instead of this:
CreateMap<LocationType, LocationTypeBL>();
CreateMap<LocationTypeBL, LocationType>();
You can just do this to accomplish the same thing:
Mapper.CreateMap<LocationType, LocationTypeBL>().ReverseMap();

Categories