Is it possible to map one object to several objects using automapper? I know that it is possible to do it the other way around as shown here.
One way to do it:
Mapper.CreateMap<MAINSource, MAINDest>()
.ForMember(dest => dest.Inner1, expression => expression.ResolveUsing(source1 => source1.Inner1))
.ForMember(dest => dest.Inner2, expression => expression.ResolveUsing(source1 => source1.Inner2));
Mapper.CreateMap<Source1, Dest1>()
.ForMember(dest => dest.NumValue, expression => expression.ResolveUsing(source1 => source1.NumValue));
Mapper.CreateMap<Source1, Dest2>()
.ForMember(dest => dest.StringValue, expression => expression.ResolveUsing(source1 => source1.StringValue));
Full example:
public class Source1
{
public int NumValue { get; set; }
public string StringValue { get; set; }
}
public class MAINSource
{
public Source1 Inner1 { get; set; }
public Source1 Inner2 { get; set; }
}
public class Dest1
{
public int NumValue { get; set; }
}
public class Dest2
{
public string StringValue { get; set; }
}
public class MAINDest
{
public Dest1 Inner1 { get; set; }
public Dest2 Inner2 { get; set; }
}
Mapper.CreateMap<MAINSource, MAINDest>()
.ForMember(dest => dest.Inner1, expression => expression.ResolveUsing(source1 => source1.Inner1))
.ForMember(dest => dest.Inner2, expression => expression.ResolveUsing(source1 => source1.Inner2));
Mapper.CreateMap<Source1, Dest1>()
.ForMember(dest => dest.NumValue, expression => expression.ResolveUsing(source1 => source1.NumValue));
Mapper.CreateMap<Source1, Dest2>()
.ForMember(dest => dest.StringValue, expression => expression.ResolveUsing(source1 => source1.StringValue));
var innerSource = new Source1 {NumValue = 1, StringValue = "supervalue"};
var mainSource = new MAINSource
{
Inner1 = innerSource,
Inner2 = innerSource
};
var destination = Mapper.Map<MAINSource, MAINDest>(mainSource);
destination.Inner1.NumValue.ShouldEqual(1);
destination.Inner2.StringValue.ShouldEqual("supervalue");
Related
The basic problem is that I have properties on my source parent entities that I need to map to my destination child DTOs when the child entities are mapped to the destination child DTOs. The child entities do not have navigation properties to their parent. As you can see below, the parent has a field of the child type. You can see in the code below that ParentSource has a property called TitleImage of type ImageSource. So linkage is one directional. In my code, I use the ImageSource everywhere. There are at least 15 different entities with properties of type ImageSource. I normalized my database. The ImageSource entity does not have X and Y coordinates because only a few, maybe 5 out of 30, places actually need those extra values. In those few places, I added the X and Y on the parent as you see on the ParentSource class with the TitleImageX and TitleImageY.
I could easily do what I want with a loop, and that is what I am doing now, but I would love to use Automapper for this if I could. It would be way less code.
Here are my classes:
public class ParentSource
{
public string Id { get; set; }
public string Name { get; set; }
public ImageSource HomeImage { get; set; }
public ImageSource TitleImage { get; set; }
//These should be copied to the child object
public int TitleImageX { get; set; }
public int TitleImageY { get; set; }
}
public class ParentDest
{
public string ParentId { get; set; }
public string DisplayName { get; set; }
public ImageDest HomeImage { get; set; }
public ImageDest TitleImage { get; set; }
}
public class ImageSource
{
public string Id { get; set; }
public string Url { get; set; }
public decimal Height { get; set; }
public decimal Width { get; set; }
}
public class ImageDest
{
public string ImageId { get; set; }
public string ImageUrl { get; set; }
public decimal Height { get; set; }
public decimal Width { get; set; }
//Not all images have Coordinate
public decimal XCoordinate { get; set; }
public decimal YCoordinate { get; set; }
}
Here are what I have so far for doing the mappings. I don't know how to copy the X & Y coordinates from the parent to the child.
CreateMap<ParentSource, ParentDest>()
.ForMember(
dest => dest.ParentId,
opt => opt.MapFrom(src => src.Id))
.ForMember(
dest => dest.DisplayName,
opt => opt.MapFrom(src => src.Name));
CreateMap<ImageSource, ImageDest>()
.ForMember(
dest => dest.ImageId,
opt => opt.MapFrom(src => src.Id))
.ForMember(
dest => dest.ImageUrl,
opt => opt.MapFrom(src => src.Url));
Thank you for your time and help.
Or you can do it in config (not tried or tested) which you may prefer, but there is no check for null on TitleImage
CreateMap<ParentSource, ParentDest>()
.ForMember(
dest => dest.ParentId,
opt => opt.MapFrom(src => src.Id))
.ForMember(
dest => dest.DisplayName,
opt => opt.MapFrom(src => src.Name))
.ForMember(dest=>dest.TitleImage,
opt=>opt.MapFrom(src=>new ImageDest
{
Width = src.TitleImage.Width,
Height = src.TitleImage.Height,
ImageId = src.TitleImage.Id,
ImageUrl = src.TitleImage.Url,
XCoordinate = src.TitleImageX,
YCoordinate = src.TitleImageY
}
));
From what I gather you want the X&Y from the parent into the child. You can use a converter class for this.
Converter:
public class ParentConverter : ITypeConverter<ParentSource, ParentDest>
{
public ParentDest Convert(ParentSource source, ParentDest destination, ResolutionContext context)
{
if (destination == null)
destination = new ParentDest();
destination.DisplayName = source.Name;
destination.ParentId = source.Id;
if (source.TitleImage != null)
{
destination.TitleImage = context.Mapper.Map<ImageDest>(source.TitleImage);
destination.TitleImage.XCoordinate = source.TitleImageX;
destination.TitleImage.YCoordinate = source.TitleImageY;
}
return destination;
}
}
Mapping:
CreateMap<ParentSource, ParentDest>()
.ConvertUsing<ParentConverter>();
CreateMap<ImageSource, ImageDest>()
.ForMember(
dest => dest.ImageId,
opt => opt.MapFrom(src => src.Id))
.ForMember(
dest => dest.ImageUrl,
opt => opt.MapFrom(src => src.Url);
});
Development environment:
AutoMapper:7.0.1
NetCore:2.1
Wrong content:
Unmapped members were found. Review the types and members below.
Add a custom mapping expression, ignore, add a custom resolver, or modify the source/destination type
For no matching constructor, add a no-arg ctor, add optional arguments, or map all of the constructor parameters
My Model:
public partial class XsOrdersitems
{
public int Id { get; set; }
public string ParentNo { get; set; }
public string GoodsSn { get; set; }
public string Name { get; set; }
public string Barcode { get; set; }
}
My DTO type:
public class DTOOrderItem
{
public string OrderPaNo { get; set; }
public string OrderNo { get; set; }
public string OrderNe { get; set; }
public string OrderComm { get; set; }
}
My mapping configuration:
reateMap<DTOOrderItem, XsSalesitems>()
.ForMember(d => d.Id, opt =>opt.Ignore())
.ForMember(d => d.Name, opt => { opt.MapFrom(s => s.OrderNe); })
.ForMember(d => d.GoodsSn, opt => { opt.MapFrom(s => s.OrderNo);})
.ForMember(d => d.ParentNo, opt => { opt.MapFrom(s =>s.OrderPaNo);})
.ForMember(d => d.Comment, opt => { opt.MapFrom(s => s.OrderComm); });
I tried to use opt. Ignore() to ignore the unconfigured mapping attributes, but still reported the above error, please help me, thank you for your time to answer my question.
Per your examples you are mapping to the wrong model. Your CreateMap definition is mapping DTOOrderItem to XsSalesitems, but your comments suggest you are wanting to map to XsOrderitems. Might try adding an additional CreateMap<DTOOrderItem, XsOrderitems>()... with the necessary .Ignore() references and you should be good to go.
Complete mapping definition:
CreateMap<DTOOrderItem, XsOrderitems>()
.ForMember(d => d.Id, opt => opt.Ignore())
.ForMember(d => d.Name, opt => opt.MapFrom(s => s.OrderNe))
.ForMember(d => d.GoodsSn, opt => opt.MapFrom(s => s.OrderNo))
.ForMember(d => d.ParentNo, opt => opt.MapFrom(s =>s.OrderPaNo))
.ForMember(d => d.Comment, opt => opt.MapFrom(s => s.OrderComm));
I need to map to properties that are both collections of objects of the same type.
For example:
Destination:
public class MyNewObject
{
Collection TypeCollection1 { get; set; }
Collection TypeCollection2 { get; set; }
}
public class MyType
{
string Field1 { get; set; }
string Field2 { get; set; }
}
Source:
public class MyLegacyObject
{
Collection LegacyCollection1 { get; set; }
Collection LegacyCollection2 { get; set; }
}
public class MyLegacyType
{
string OldField1 { get; set; }
string OldField2 { get; set; }
}
For non-collections, I'm used to doing something like this:
Mapper.CreateMap()
.ForMember(dest => dest.TypeCollection1, opt => opt.MapFrom(
src => new Collection
{
// is there some kind of ForEach thing I can do???
Field1 = src.OldField1,
Field2 = src.OldField2
??? // this obviously doesn't work because these are the properties on MyType, not the collection
}));
Mapper.CreateMap<MyLegacyType, MyType>()
.ForMember(dest => dest.Field1, opt => opt.MapFrom(src => src.OldField1))
.ForMember(dest => dest.Field2, opt => opt.MapFrom(src => src.OldField2));
Mapper.CreateMap<MyLegacyObject, MyNewObject>()
.ForMember(dest => dest.TypeCollection1, opt => opt.MapFrom(src => src.LegacyCollection1))
.ForMember(dest => dest.TypeCollection2, opt => opt.MapFrom(src => src.LegacyCollection2))
I think I solved my own issue. This should do it.
i have problem with automapper for convert property with TypeName start with Tbl in Source and property start with FK in destination, but this error occured:
Missing type map configuration or unsupported mapping. Tbl_Child -> Int32 Tbl_Child -> System.Int32 Destination path: Destination.FK_Child.FK_Child Source value: Tbl_Child
code is:
public class Source
{
public Tbl_Child Child { get; set; }
public string SourceName { get; set; }
}
public class Tbl_Child
{
public int ID_Child { get; set; }
public string ChildName { get; set; }
}
public class Destination
{
public int FK_Child { get; set; }
public string ChildName { get; set; }
public string SourceName { get; set; }
}
static void Main(string[] args)
{
var src = new Source()
{
Child = new Tbl_Child()
{
ChildName = "ch",
ID_Child = 1
},
SourceName = "src"
};
AutoMapper.Mapper.CreateMap<Source, Destination>();
var dest = AutoMapper.Mapper.Map<Source, Destination>(src);
Console.ReadKey();
}
i test AutoMapper version 2.0.0.0, and 3.1.1.0
Out of the box, Automapper is able to map properties with the same name and flattening in properties. For other properties you must specify the mapping that you want.
For example mapping from Source.Child.ID_Child to Destination.FK_Child would be accomplished like this:
Mapper.CreateMap<Source, Destination>()
.ForMember(d => d.FK_Child, o => o.MapFrom(s => s.Child.ID_Child));
And you can chain all your custom mappings like so
Mapper.CreateMap<Source, Destination>()
.ForMember(d => d.FK_Child, o => o.MapFrom(s => s.Child.ID_Child))
.ForMember(d => d.ChildName, o => o.MapFrom(s => s.Child.ChildName));
This mapping should be enough in this case but I think you may need to read the wiki a bit before going on
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"));
}
}