I'm facing a problem with AutoMapper 2.1.267.0 when working with objects containing collections of derived classes.
I've isolated my problem in a simpler scenario with the following classes:
public class ClassABase
{
public string Name { get; set; }
}
public class ClassA : ClassABase
{
public string Description { get; set; }
}
public class ClassBBase
{
public string Title { get; set; }
}
public class ClassB : ClassBBase
{
public string Text { get; set; }
}
public class ContainerA
{
public IList<ClassA> ClassAList { get; set; }
public ClassA ClassA { get; set; }
}
public class ContainerB
{
public IList<ClassB> ClassBList { get; set; }
public ClassB ClassB { get; set; }
}
and these mappings
public class ClassABaseToClassBBase : Profile
{
protected override void Configure()
{
CreateMap<ClassABase, ClassBBase>()
.Include<ClassA, ClassB>()
.ForMember(dest => dest.Title, opt => opt.MapFrom(src => src.Name));
Mapper.AssertConfigurationIsValid();
}
}
public class ClassAToClassB : Profile
{
protected override void Configure()
{
CreateMap<ClassA, ClassB>()
.ForMember(dest => dest.Text, opt => opt.MapFrom(src => src.Description));
Mapper.AssertConfigurationIsValid();
}
}
public class ContainerAToContainerB : Profile
{
protected override void Configure()
{
CreateMap<ContainerA, ContainerB>()
.ForMember(dest => dest.ClassBList,
opt => opt.MapFrom(src => Mapper.Map<IList<ClassA>, IList<ClassB>>(src.ClassAList)))
.ForMember(dest => dest.ClassB, opt => opt.MapFrom(src => src.ClassA));
}
}
Here is the initialization
Mapper.Initialize(x =>
{
x.AddProfile<ClassABaseToClassBBase>();
x.AddProfile<ClassAToClassB>();
x.AddProfile<ContainerAToContainerB>();
});
and a diagram summarizing everything (Red arrows represent AutoMapper mappings)
I've mapped a ContainerA instance to ContainerB. This first scenario works properly. The destination object is fully filled as shown in the image bellow:
But if I add this mapping
public class ClassBBaseToClassB : Profile
{
protected override void Configure()
{
CreateMap<ClassBBase, ClassB>()
.ForMember(dest => dest.Text, opt => opt.Ignore());
Mapper.AssertConfigurationIsValid();
}
}
Mapper.Initialize(x =>
{
x.AddProfile<ClassABaseToClassBBase>();
x.AddProfile<ClassAToClassB>();
x.AddProfile<ClassBBaseToClassB>();
x.AddProfile<ContainerAToContainerB>();
});
the result is
The "Text" properties of all elements of the collection become null. As you can see in the image, the "Text" property of the containerB.ClassB object is still mapped correctly. This only occurs with collections. I don't know what's happening. Any clues?
Thanks
Hi,
Try to change the mapping between containers in this way :
public class ContainerAToContainerB : Profile
{
protected override void Configure()
{
CreateMap<ContainerA, ContainerB>()
.ForMember(dest => dest.ClassBList,opt=>opt.MapFrom(src=>src.ClassAList))
.ForMember(dest => dest.ClassB, opt => opt.MapFrom(src => src.ClassA));
}
}
Related
I want map Origin.CityId and Origin.StateId properties of
Itinerary class to OriginCityId and OriginStateId properties
of ItineraryModel class.
Ex: Itinerary itinerary = Mapper.Map<Itinerary>(ItineraryModel);
My ViewModel
public class ItineraryModel : BaseModel
{
public int OriginCityId { get; set; }
public int OriginStateId { get; set; }
public bool Published { get; set; }
}
My Entity
public class Itinerary : BaseEntity
{
public City Origin { get; set; }
public bool Published { get; set; }
}
My mapping that tried do
public class MappingProfile : Profile
{
public MappingProfile()
{
CreateMap<ItineraryModel, Itinerary>()
.ForPath(x => x.Origin.CityId, opt => opt.MapFrom(src => src.OriginCityId))
.ForPath(x => x.Origin.StateId, opt => opt.MapFrom(src => src.OriginStateId))
.ReverseMap();
}
}
I would like to too .ReverseMap() but can't find right syntax.
You need to add two mappings for mapping to Itenerary
CreateMap<ItineraryModel, City>()
.ForMember(city => city.CityId, expression => expression.MapFrom(itineraryModel => itineraryModel.OriginCityId))
.ForMember(city => city.StateId, expression => expression.MapFrom(itineraryModel => itineraryModel.OriginStateId));
CreateMap<ItineraryModel, Itinerary>()
.ForMember(itinerary => itinerary.Origin, expression => expression.MapFrom(itineraryModel => itineraryModel));
Similarly you can define reverse mappings manually if needed.
BTW ReverseMap() is not recommended by author
https://jimmybogard.com/automapper-usage-guidelines/
I'm using an Automapper and I need to map a List of objects into a nested object. I have these objects:
public abstract class FooSrcBase
{
}
public class FooSrc : FooSrcBase
{
public bool Prop { get; set; }
}
public class FooDest
{
public bool Prop { get; set; }
}
public class FooDestGroup
{
public FooDest FooDest { get; set; }
}
public class Dest
{
public FooDestGroup FooDestGroup { get; set; }
}
I have IEnumerable<FooSrc> which contains FooSrc objects (there are many implementations and only one object per each type may exist in the source) and I need to map it into Dest object. I need this because of mapping into the view models for front end.
When I register mapping like this:
CreateMap<IEnumerable<FooSrc>, Dest>().ForPath(dest => dest.FooDestGroup.FooDest, opt => opt.MapFrom(src => src.FirstOrDefault(x => x.GetType() == typeof(FooSrc))));
CreateMap<FooSrc, FooDest>();
When I map empty list, a problem occurs in Dest object - FooDestGroup is an instance of object, which has a property FooDest with null value.
How it would be possible to make Dest property FooDestGroup map to null, if I provide empty list as a source?
Firstly, your abstract FooSrc class will need a different name (conflicts with your concrete class name FooSrc)
How about modifying the constructor on Dest to avoid the nesting issue?
Automapper is capable of mapping to the constructor parameter automatically, but if you need more advanced behaviour you can refer to https://docs.automapper.org/en/stable/Construction.html
Something like this should work:
public class Dest
{
public FooGroup FooGroup { get; set; }
public Dest(FooDest fooDest)
{
FooGroup = new FooGroup { FooDest = fooDest };
}
}
[..]
Mapper.CreateMap<FooSrc, Dest>();
Mapper.Map<List<Dest>>(listOfFooSrc);
This fixed my problem:
CreateMap<IEnumerable<FooSrcBase>, Dest>()
.ForMember(dest => dest.FooDestGroup, opt => opt.MapFrom(src => src.FirstOrDefault(x => x.GetType() == typeof(FooSrc))));
CreateMap<FooSrc, FooDestGroup>()
.ForMember(dest => dest.FooDest, opt => opt.MapFrom(src => src));
CreateMap<FooSrc, FooDest>();
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));
I have 2 objects that I need to map to each other. They look like
public class Example1
{
CustomType1 Prop { get; set; }
List<CustomType1> List { get; set; }
}
public class Example2
{
Customtype2 Prop { get; set; }
List<Customtype2> List { get; set; }
}
public class CustomType1
{
public string SomeString { get; set; }
}
public class Customtype2
{
public string FirstPartOfSomeString { get; set; }
public string SecondPartOfSomeString { get; set; }
}
I want to make one CustomResolver that maps CustomType1 to CustomType2 and then use that resolver on the list. For example,
Mapper.CreateMap<Example1, Example2>()
.ForMember(d => d.Prop, opt => opt.ResolveUsing(myCustomResolver))
.ForMember(d => d.List, opt => opt.ResolveUsing( /*use myCustomResolver on a list here*/));
I have tried using something like:
Mapper.CreateMap<Example1, Example2>()
.ForMember(d => d.Prop, opt => opt.ResolveUsing(myCustomResolver))
.ForMember(d => d.List, opt => opt.MapFrom(s => s.List.Select(myCustomResolver.Resolve).ToList()));
but I seem to be missing something. Is there a way to do this with AutoMapper?
Have you tried adding a mapping between the custom types instead of using a resolver?
AutoMapper is intelligent enough to re-use mappings for lists...
Mapper.CreateMap<CustomType1, CustomType2>()
.ForMember(x => FirstPartOfSomeString, opts => opts.MapFrom(x => x.SomeString.Substring(5)))
.ForMember(x => SecondPartOfSomeString, opts => opts.MapFrom(x => x.SomeString.Substring(5, 5)));
Mapper.CreateMap<Example1, Example2>();
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.