I have a Domain model that countains the following properties :
public class Profile
{
public DateTime birthDate { get; set; }
public List<Language> languages { get; set; }
}
Where Language represent an enumeration defined here :
public enum Language
{
English,
French,
Spannish
}
i would like to automatically populate the two following properties based on the languages values stored inside my domain model :
public Dictionary <int, String> languages_list { get; set; }
public List<string> languages_known { get; set; }
the question is : can it be done using automapper , if so how should i proceed ?
OK, using the detail you've provided this is what I've come up with.
Classes
public class SOProfile
{
public DateTime birthDate { get; set; }
public List<Language> languages { get; set; }
}
public class WhatAmI
{
public Dictionary<int, String> languages_list { get; set; }
public List<string> languages_known { get; set; }
}
Note that Profile was renamed to SOProfile to avoid a clash with AutoMapper
AutoMapper Configuration
public class MyProfile : Profile
{
public override string ProfileName
{
get
{
return "MyProfile";
}
}
protected override void Configure()
{
Mapper.CreateMap<SOProfile, WhatAmI>()
.ForMember(dest => dest.languages_list,
opt => opt.MapFrom(
src => src.languages
.Select((x,i) => new { Item = x, Index = i})
.ToDictionary(x => x.Index,
x => x.Item.ToString())))
.ForMember(dest => dest.languages_known,
opt => opt.MapFrom(
src => src.languages
.Select(x => x.ToString()).ToList()));
}
}
Unit Tests
[TestFixture]
public class MappingTests
{
[Test]
public void AutoMapper_Configuration_IsValid()
{
Mapper.Initialize(m => m.AddProfile<MyProfile>());
Mapper.AssertConfigurationIsValid();
}
[Test]
public void AutoMapper_Mapping_IsValid()
{
Mapper.Initialize(m => m.AddProfile<MyProfile>());
Mapper.AssertConfigurationIsValid();
var profile = new SOProfile
{
birthDate = new DateTime(2012, 01, 01),
languages = new List<Language>
{
Language.English,
Language.French,
Language.English,
Language.French
}
};
var rc = Mapper.Map<SOProfile, WhatAmI>(profile);
Assert.That(rc, Is.Not.Null);
Assert.That(rc.languages_known, Is.Not.Null);
Assert.That(rc.languages_known.Count, Is.EqualTo(4));
Assert.That(rc.languages_known.Count(x => x == "English"),
Is.EqualTo(2));
Assert.That(rc.languages_known.Count(x => x == "French"),
Is.EqualTo(2));
Assert.That(rc.languages_known.Count(x => x == "Spanish"),
Is.EqualTo(0));
Assert.That(rc.languages_list, Is.Not.Null);
Assert.That(rc.languages_list.Count, Is.EqualTo(4));
Assert.That(rc.languages_list.First(x => x.Key == 0).Value,
Is.EqualTo("English"));
Assert.That(rc.languages_list.First(x => x.Key == 1).Value,
Is.EqualTo("French"));
Assert.That(rc.languages_list.First(x => x.Key == 2).Value,
Is.EqualTo("English"));
Assert.That(rc.languages_list.First(x => x.Key == 3).Value,
Is.EqualTo("French"));
}
}
Related
I have a class for CountryAndCity:
public class CountryAndCity
{
public Country Country { get; set; }
public City City { get; set; }
public ZipCode ZipCode { get; set; }
}
I am using this class in several output classes, for example:
public class OutputClassA
{
public CountryAndCity CountryAndCity { get; set; }
}
public class OutputClassB
{
public CountryAndCity CountryAndCity { get; set; }
}
In addition, i have some "Input Classes" which includes the same relevant fields for mapping with additional other fields:
public class InputClassA : ICountryAndCity
{
...Some other properties.....
public int? CountryId { get; set; }
public string CountryDesc { get; set; }
public int? CityId { get; set; }
public string CityDesc { get; set; }
...Some other properties.....
}
public class InputClassB : ICountryAndCity
{
...Some other properties.....
public int? CountryId { get; set; }
public string CountryDesc { get; set; }
public int? CityId { get; set; }
public string CityDesc { get; set; }
...Some other properties.....
}
i didn't want to duplicate the code for every CreateMap statement for CountryAndCity property mapping for every Input and output combination types so i decided to use an interface that all the "input types" implements.
i created this configuration and its working great if i implement ICountryAndCity on all the relevant "input classes" and using "Mapper.Map" for the interface inside the CreateMap function.
Mapper.Initialize(cfg =>
{
cfg.CreateMap<InputClassA, OutputClassA>()
.ForMember(dest => dest.CountryAndCity,
opts => opts.MapFrom(src => Mapper.Map<ICountryAndCity, CountryAndCity>((ICountryAndCity)src)));
cfg.CreateMap<InputClassB, OutputClassB>()
.ForMember(dest => dest.CountryAndCity,
opts => opts.MapFrom(src => Mapper.Map<ICountryAndCity, CountryAndCity>((ICountryAndCity)src)));
cfg.CreateMap<ICountryAndCity, CountryAndCity>()
.ForMember(dest => dest.Country,
opts => opts.MapFrom(
src => new Country
{
CountryId = src.CountryId,
CountryDesc = src.CountryDesc
}))
.ForMember(dest => dest.City,
opts => opts.MapFrom(
src => new City
{
CityId = src.CityId,
CityDesc = src.CityDesc
}))
.ForMember(dest => dest.ZipCode,
opts => opts.MapFrom(
src => new ZipCode
{
ZipCodeId = src.ZipCodeId,
ZipCodeDesc = src.ZipCodeDesc
}));
}
I am sure that there is other better way to do it using AutoMapper without using interface. Can someone help with that?
Having an issue with version 6.1.1. In the below, the result of the reverse map still has the Company object populated. Per this post, which shows what I am doing below, except they are ignoring a property, and I'm ignoring a complex object.
What am I missing?
CreateMap<Item, ItemViewModel>(MemberList.Destination)
.ReverseMap()
.ForMember(x => x.Company, x => x.Ignore())
;
With AutoMapper 6.1 you could use ForPath instead ForMember to ignore complex objects.
See How to ignore property with ReverseMap for further information.
I see not what is wrong, but here is a running sample:
namespace AutomapperTest2
{
internal class Program
{
#region Methods
private static void Main(string[] args)
{
// Configure the mappings
Mapper.Initialize(cfg =>
{
cfg.CreateMap<ApplicantEducation, ApplicantEducationVM>();
cfg.CreateMap<Applicant, ApplicantVM>().ReverseMap()
.ForMember(x => x.Education, x => x.Ignore());
});
var config = new MapperConfiguration(cfg => cfg.CreateMissingTypeMaps = true);
var mapper = config.CreateMapper();
Applicant ap = new Applicant
{
Name = "its me",
Education =
new ApplicantEducation
{
SomeInt = 10,
SomeString = "sampleString"
}
};
// Map
ApplicantVM apVm = Mapper.Map<Applicant, ApplicantVM>(ap);
Applicant apBack = Mapper.Map<ApplicantVM, Applicant>(apVm);
}
#endregion
}
/// Your source classes
public class Applicant
{
public ApplicantEducation Education { get; set; }
public string Name { get; set; }
}
public class ApplicantEducation
{
public int SomeInt { get; set; }
public string SomeString { get; set; }
}
// Your VM classes
public class ApplicantVM
{
public string Description { get; set; }
public ApplicantEducationVM Education { get; set; }
public string Name { get; set; }
}
public class ApplicantEducationVM
{
public int SomeInt { get; set; }
public string SomeString { get; set; }
}
}
}
I have a destination class like below
public class Order
{
public string OrderId { get; set; }
public List<Delivery> Deliveries { get; set;}
}
public class Delivery
{
public string ProductName { get; set; }
}
I've to map ProductName in above class from below source class
public class OrderDTO
{
public string OrderId { get; set; }
public List<OrderDelivery> Deliveries { get; set; }
}
public class OrderDelivery
{
public List<OrderDeliveryDetails> ProductDeliveryDetails { get; set; }
}
public class OrderDeliveryDetails
{
public string ProductName { get; set; }
}
How I can do this using Automapper.
(Note: Please Don't confuse with the List<OrderDeliveryDetails> in OrderDeliveryclass. It is because, it may have child products as well, but i need to take parent ProductNameonly)
Well if by taking the parent ProductName you mean the first, you can try something along these lines (otherwise I might've misunderstood your question and you can ignore my answer):
public class AutoMapperConfiguration : Profile
{
private readonly IConfiguration _mapper;
public AutoMapperConfiguration(IConfiguration mapper)
{
_mapper = mapper;
}
protected override void Configure()
{
_mapper.CreateMap<OrderDTO, Order>
.ForMember(d => d.OrderId, o => o.MapFrom(s => s.OrderId))
.ForMember(d => d.Deliveries, o => o.MapFrom(s => s.Deliveries));
_mapper.CreateMap<OrderDelivery, Delivery>
.ForMember(d => d.ProductName, o => o.MapFrom(s => s.ProductDeliveryDetails.First().ProductName));
}
}
and then simply
_mapper.Map(orderDto);
(this is, provided you have all the wireup needed to use AutoMapper).
I am trying to map two objects that are mostly similar with AutoMapper but one member (AudioSummary) raises the following exception :
The following property on EchoNestModel.AudioSummary cannot be mapped: AudioSummary
Add a custom mapping expression, ignore, add a custom resolver, or modify the destination type EchoNestModel.AudioSummary.
Context:
- Mapping to property AudioSummary from EchoNest.Api.AudioSummary to EchoNestModel.AudioSummary
- Mapping from type EchoNest.Api.TrackProfile to EchoNestModel.Profile
Exception of type 'AutoMapper.AutoMapperConfigurationException' was thrown.
Mapping definition
var map = Mapper.CreateMap<TrackProfile, Profile>();
map.ForMember(dest => dest.ForeignIds, opt => opt.ResolveUsing<ForeignIdResolver>());
map.ForMember(dest => dest.ForeignReleaseIds, opt => opt.ResolveUsing<ForeignReleaseIdResolver>());
map.ForMember(s => s.Media, t => t.Ignore());
map.ForMember(s => s.ProfileId, t => t.Ignore());
map.ForMember(s => s.AudioSummary, t => t.MapFrom(s => s.AudioSummary));
I've added the following two lines but a totally different error occurs :
map.ForMember(s => s.AudioSummary.Profile, t => t.Ignore());
map.ForMember(s => s.AudioSummary.AudioSummaryId, t => t.Ignore());
Expression 's => s.AudioSummary.Profile' must resolve to top-level member and not any child object's properties.
Use a custom resolver on the child type or the AfterMap option instead.
Parameter name: lambdaExpression
How can I successfully map AudioSummary ?
Source object
Target object
EDIT: In general, try AutoMapper.Mapper.AssertConfigurationIsValid();, this will show you all possible problems in your mapper setup.
From the information you provided, it looks like you need to define map for the AudioSummary classes (dest and source) as well:
[TestFixture]
public class MappingTest
{
public class SourceAudioSummary
{
public int Id { get; set; }
public string OtherData { get; set; }
}
public class TrackProfile
{
public string Whatever { get; set; }
public SourceAudioSummary AudioSummary { get; set; }
}
public class DestAudioSummary
{
public int Id { get; set; }
public string OtherData { get; set; }
}
public class Profile
{
public string Whatever { get; set; }
public DestAudioSummary AudioSummary { get; set; }
}
[Test]
public void Mapping()
{
Mapper.CreateMap<SourceAudioSummary, DestAudioSummary>();
Mapper.CreateMap<TrackProfile, Profile>();
var trackProfile = new TrackProfile
{
Whatever = "something",
AudioSummary = new SourceAudioSummary
{
Id = 1,
OtherData = "other"
}
};
var profile = Mapper.Map<TrackProfile, Profile>(trackProfile);
Assert.That(profile.Whatever == "something");
Assert.That(profile.AudioSummary.Id == 1);
Assert.That(profile.AudioSummary.OtherData == "other");
}
}
I am trying to map between two lists of objects. The source type has a complex property of type A; the destination type is a flattened subset of type A plus an additional scalar property that is in the source type.
public class A
{
public int Id { get; set; }
public string Name { get; set; }
}
public class Source
{
public A MyA { get; set; }
public int SomeOtherValue { get; set; }
}
public class Destination
{
public string Name { get; set; }
public int SomeOtherValue { get; set; }
}
If it's not clear, I'd like Source.MyA.Name to map to Destination.Name and Source.SomeOtherValue to map to Destination.SomeOtherValue.
In reality, type A has a dozen or so properties, about which 80% map over to properties of the same name in Destination. I can get things to work if I explicitly spell out the mappings in CreateMap like so:
CreateMap<Source, Destination>()
.ForMember(dest => dest.Name, opt => opt.MapFrom(src => src.MyA.Name));
The downside here is I want to avoid having to add a ForMember line for each of A's properties that need to get copied over to Destination. I was hoping I could do something like:
CreateMap<Source, Destination>()
.ForMember(dest => dest, opt => opt.MapFrom(src => src.MyA));
But if I try the above I get a runtime error when the mapping is registered: "Custom configuration for members is only supported for top-level individual members on a type."
Thanks
create mappings between A and Destination, and Source and Destination, and then use AfterMap() to use first mapping in second
Mapper.CreateMap<A, Destination>();
Mapper.CreateMap<Source, Destination>()
.AfterMap((s, d) => Mapper.Map<A, Destination>(s.MyA, d));
then use it like this:
var res = Mapper.Map<Source, Destination>(new Source { SomeOtherValue = 7, MyA = new A { Id = 1, Name = "SomeName" } });
As a workaround you can use custom type converter with additional property in the destination type to avoid recursion.
[TestFixture]
public class MapComplexType
{
[Test]
public void Map()
{
Mapper.CreateMap<A, Destination>();
Mapper.CreateMap<Source, Destination>().ConvertUsing(new TypeConvertor());
var source = new Source
{
MyA = new A
{
Name = "Name"
},
SomeOtherValue = 5
};
var dest = new Destination();
Mapper.Map(source, dest);
Assert.AreEqual(dest.Name, "Name");
}
}
public class TypeConvertor : ITypeConverter<Source, Destination>
{
public Destination Convert(ResolutionContext context)
{
var destination = (Destination) context.DestinationValue;
if (!((Destination)context.DestinationValue).IsMapped || destination == null)
{
destination = destination ?? new Destination();
destination.IsMapped = true; // To avoid recursion
Mapper.Map((Source)context.SourceValue, destination);
destination.IsMapped = false; // If you want to map the same object few times
}
Mapper.Map(((Source)context.SourceValue).MyA, destination);
return (Destination)context.DestinationValue;
}
}
public class A
{
public int Id { get; set; }
public string Name { get; set; }
}
public class Source
{
public A MyA { get; set; }
public int SomeOtherValue { get; set; }
}
public class Destination
{
public string Name { get; set; }
public int SomeOtherValue { get; set; }
// Used only for mapping purposes
internal bool IsMapped { get; set; }
}
Try this,
Mapper.CreateMap<A, Destination>();
Mapper.CreateMap<Source, Destination>()
.ForMember(destination => destination.Name, options => options.MapFrom(source => Mapper.Map<A, Destination>(source.MyA).Name));
var objSource = new Source { SomeOtherValue = 7, MyA = new A { Id = 1, Name = "SomeName" } };
var result = Mapper.Map<Source, Destination>(objSource);