I want to like merge those Source objects into a List<Destination>. Notice that SourceParent and Destination Id property MUST be the same.
var parent = new SourceParent
{
Id = 1,
Childs = new List<SourceChild>
{
new SourceChild { ChildId = 12, OtherProperty = "prop1" },
new SourceChild { ChildId = 13, OtherProperty = "prop2" },
new SourceChild { ChildId = 14, OtherProperty = "prop3" },
}
};
Mapper.Initialize(cfb =>
{
cfb.CreateMap<SourceParent, List<Destination>>()
.ForMember(dest => dest, opt => opt.MapFrom(src => src.Childs));
cfb.ValidateInlineMaps = false;
});
List<Destination> destination = Mapper.Map<SourceParent, List<Destination>>(parent);
Classes:
public class SourceParent
{
public int Id { get; set; }
public List<SourceChild> Childs { get; set; }
}
public class SourceChild
{
public string OtherProperty { get; set; }
public int ChildId { get; set; }
}
public class Destination
{
public int SourceParentId { get; set; }
public string OtherProperty { get; set; }
public int ChildId { get; set; }
}
Is there a way to create a mapping rule for this case? Is it even possible?
I think your best option here is to define a TypeConverter.
You can do TypeConverters inline like I've done below or you can define a class that implements the ITypeConverter<TIn, TOut> interface.
cfb.CreateMap<SourceParent, List<Destination>>().ConvertUsing((src, dest, context) =>
{
return src.Childs.Select(x =>
{
var destination = context.mapper.Map<Destination>(x);
destination.SourceParentId = src.Id;
return destination;
}
});
If you wanted to (I usually stay away from this because it can get unruly fast) you could define another custom mapping using a tuple or a wrapper class like this.
cfb.CreateMap<SourceParent, List<Destination>>().ConvertUsing((src, dest, context) =>
{
return src.Childs.Select(x => context.mapper.Map<Destination>((src.Id, x)))
});
cfb.CreateMap<(int partentId, SourceChild child), Destination>()
.ForMember(dest => dest.Id, opt => opt.MapFrom(src => src.parentId))
.ForMember(dest => dest.ChildId, opt => opt.MapFrom(src => src.child.Id))
.ForMember(dest => dest.OtherProperty , opt => opt.MapFrom(src => src.child.OtherProperty ));
This can be nice for small examples but if you are doing it often it can lead to really cluttered mapper configurations (in my opinion), but it does simplify your type converter.
Related
I wanna map Person to list of Client with AutoMapper:
and this is my models:
public class Person
{
public Guid Id { get; set;}
public string Name { get; set;}
public string Country { get; set;}
public string PhoneNumber { get; set;}
}
public class Member
{
public Guid Id { get; set;}
public string FullName { get; set; }
}
public class Client
{
public Member User { get; set; }
}
I tried to do it with AutoMapper but I couldn't:
CreateMap<Person, List<Client>>();
You need 4 mapping rules:
Map Person to Member.
Map Member to Client.
Map Person to Client.
Map Person to List<Client>.
CreateMap<Person, Member>()
.ForMember(dest => dest.FullName, opt => opt.MapFrom(src => src.Name));
CreateMap<Member, Client>()
.ForMember(dest => dest.User, opt => opt.MapFrom(src => src));
CreateMap<Person, Client>()
.ConvertUsing((src, dest, ctx) => new Client { User = ctx.Mapper.Map<Member>(src) });
CreateMap<Person, List<Client>>()
.ConvertUsing((src, dest, ctx) => new List<Client> { ctx.Mapper.Map<Client>(src) });
Demo # .NET Fiddle
You can map them using the following code:
CreateMap<Person, Client>()
.ForMember(dest => dest.User, opt => opt.MapFrom(src => new Member { Id = src.Id, FullName = src.Name }));
CreateMap<Person, List<Client>>()
.ConvertUsing(src => src.Select(x => new Client { User = new Member { Id = x.Id, FullName = x.Name } }).ToList());
I'm kind of new in the world of AutoMapper, just so you know =)
I have 2 classes:
Class LibraryParameters
public class LibraryParameters
{
public int library_id { get; set; }
public string document_name { get; set; } = string.Empty;
public string template_name { get; set; } = string.Empty;
}
Class LibraryDocument
public class LibraryDocument
{
public int libraryId { get; set; }
public string documentName { get; set; } = string.Empty;
public string templateName { get; set; } = string.Empty;
}
So as you can see the variable names are different. So I use AutoMapper for this problem. I configured AutoMapper and made use of .ForMember as you can see below:
CreateMap<LibraryParameters, LibraryDocument>()
.ForMember(dest => dest.libraryId,
opt => opt.MapFrom(src => src.library_id))
.ForMember(dest => dest.templateName,
opt => opt.MapFrom(src => src.template_name))
.ForMember(dest => dest.documentName,
opt => opt.MapFrom(src => src.document_name));
But is it not possible to avoid these different ForMember methods and use ForAllMembers for example? I can't find any information about this anywhere so you guys are my source of help :)
Instead of using .ForAllMembers(), you need to specify the naming convention for the source as below:
For MapperConfiguration
MapperConfiguration _config = new MapperConfiguration(cfg => {
cfg.SourceMemberNamingConvention = new LowerUnderscoreNamingConvention();
});
For Mapping Profile
public class YourProfile : Profile
{
public YourProfile()
{
SourceMemberNamingConvention = new LowerUnderscoreNamingConvention();
}
}
Demo # .NET Fiddle
References
Naming Conventions | AutoMapper
I want to map source inner classes to string and in other case string array but in both cases they are mapped to null. I want MapNoteFromInnerSourceEntity1 to hold a value of InnerSourceEntity1 Id property and MapValueFromInnerSourceEntity2 to hold values of InnerSourceEntity2 value properties. So far automapper is quite difficult for me to understand.
Code:
internal class Program
{
public class InnerSourceEntity1
{
public string Id { get; set; }
public string Note { get; set; }
}
public class InnerSourceEntity2
{
public string Id { get; set; }
public string Value { get; set; }
}
public class SourceEntity
{
public InnerSourceEntity1 A { get; set; }
public IList<InnerSourceEntity2> B { get; set; }
}
public class DestinationEntity
{
public string MapNoteFromInnerSourceEntity1 { get; set; }
public string[] MapValueFromInnerSourceEntity2 { get; set; }
}
static void Main()
{
var source = new SourceEntity
{
A = new InnerSourceEntity1 { Note = "Note", Id = "Id"},
B = new List<InnerSourceEntity2> { new InnerSourceEntity2 { Id = "Id", Value = "Value" }, new InnerSourceEntity2 { Id = "Id", Value = "Value" } }
};
var config = new MapperConfiguration(cfg => {
cfg.CreateMap<SourceEntity, DestinationEntity>();
cfg.CreateMap<InnerSourceEntity1, string>().ConvertUsing(s => s.Note);
cfg.CreateMap<IList<InnerSourceEntity2>, string[]>();
});
var mapper = new Mapper(config);
DestinationEntity destination = mapper.Map<SourceEntity, DestinationEntity>(source);
Console.ReadLine();
}
}
Since the names of properties between source type SourceEntity and destination type DestinationEntity don't match, you'll have to explicitly indicate them, otherwise AutoMapper will not know how to fill the properties:
cfg.CreateMap<SourceEntity, DestinationEntity>()
.ForMember(
dst => dst.MapNoteFromInnerSourceEntity1,
opts => opts.MapFrom(src => src.A))
.ForMember(
dst => dst.MapValueFromInnerSourceEntity2,
opts => opts.MapFrom(src => src.B));
Also, don't map between concrete collection types:
cfg.CreateMap<IList<InnerSourceEntity2>, string[]>(); // <== Don't do that.
Instead, see what the docs say about mapping collections:
(...) it’s not necessary to explicitly configure list types, only their member types. ~ AutoMapper Docs
So, you only need to specify map like this:
cfg.CreateMap<InnerSourceEntity2, string>();
And since we are mapping to string we also need to instruct the AutoMapper from where it can get the string value. So, we'll use ConvertUsing() again:
cfg.CreateMap<InnerSourceEntity2, string>().ConvertUsing(s => s.Value);
Final configuration:
var config = new MapperConfiguration(cfg =>
{
cfg.CreateMap<SourceEntity, DestinationEntity>()
.ForMember(
dst => dst.MapNoteFromInnerSourceEntity1,
opts => opts.MapFrom(src => src.A))
.ForMember(
dst => dst.MapValueFromInnerSourceEntity2,
opts => opts.MapFrom(src => src.B));
cfg.CreateMap<InnerSourceEntity1, string>().ConvertUsing(s => s.Note);
cfg.CreateMap<InnerSourceEntity2, string>().ConvertUsing(s => s.Value);
});
I have something like this:
public class DomainEntity
{
public string Name { get; set; }
public string Street { get; set; }
public IEnumerable<DomainOtherEntity> OtherEntities { get; set; }
public IEnumerable<DomainAnotherEntity> AnotherEntities { get; set; }
}
public class ApiEntity
{
public string Name { get; set; }
public string Street { get; set; }
public int OtherEntitiesCount { get; set; }
}
And following mapper configuration:
Mapper.Configuration.AllowNullCollections = true;
Mapper.CreateMap<DomainEntity, ApiEntity>().
ForSourceMember(e => e.OtherEntities, opt => opt.Ignore()).
ForSourceMember(e => e.AntherEntities, opt => opt.Ignore()).
ForMember(e => e.OtherEntitiesCount, opt => opt.MapFrom(src => src.OtherEntities.Count()));
Mapper.CreateMap<ApiEntity, DomainEntity>().
ForSourceMember(e => e.OtherEntitiesCount, opt => opt.Ignore()).
ForMember(e => e.OtherEntities, opt => opt.Ignore()).
ForMember(e => e.AnotherEntities, opt => opt.Ignore());
To get the ApiEntity from the DomainEntity I'm using var apiEntity = Mapper.Map<DomainEntity, ApiEntity>(myDomainEntity);
To get the merged DomainEntity from an ApiEntity I'm using var domainEntity = Mapper.Map(myApiEntity, myDomainEntity);
But when using this, the properties OtherEntities and AnotherEntities are set to null - even when they had values before calling the mapping from myApiEntity to myDomainEntity. How can I avoid this so they really merge and not just replacing values?
Thanks for any help.
I think you're looking for UseDestinationValue instead of Ignore:
Mapper.CreateMap<ApiEntity, DomainEntity>().
ForSourceMember(e => e.OtherEntitiesCount, opt => opt.UseDestinationValue()).
ForMember(e => e.OtherEntities, opt => opt.UseDestinationValue()).
ForMember(e => e.AnotherEntities, opt => opt.UseDestinationValue());
I have linked list kind of situation. My DTO looks like this -
public class DTOItem
{
public string ID
{
get;
set;
}
public int? UniqueId
{
get;
set;
}
public string Payload
{
get;
set;
}
//How do I map this guy? It is list of same type.
public List<DTOItem> RelatedItems
{
get;
set;
}
}
How do I map this guy using AutoMapper? I am able to map other members of the class. Data is mapped from another class' collection object that has a different set of member not identical to this class.
public List<DTOItem> RelatedItems
{
get;
set;
}
Thanks in advance.
UPDATE: Here is the code -
Raphael, here is the code:
The Source Objects:
public class ResultsSet
{
public int? ResultId
{
get;
set;
}
public int UID
{
get;
set;
}
//Returns large XML string
public string ResultBlob
{
get;
set;
}
public RelatedItems[] RelatedSet
{
get;
set;
}
}
public class RelatedItems
{
public int Item_ID
{
get;
set;
}
public int Relationship_ID
{
get;
set;
}
public string Description
{
get;
set;
}
}
To map here is the code:
Mapper.CreateMap<ResultSet, DTOItem>()
.ForMember(dest => dest.ID, opt => opt.MapFrom(src => src.ResultID.GetValueOrDefault(0)))
.ForMember(dest => dest.UniqueId, opt => opt.MapFrom(src => src.UID))
.ForMember(dest => dest.Payload, opt => opt.MapFrom(src => src.ResultBlob));
/*
How do I map RelatedSet to RelatedItems here?
*/
Mapper.Map(result, returnResult);
Thanks again.
No need to use AutoMapper for this.
For non-cyclic, relatively flat data, this should do:
static Func<RelatedItems, DTOItem> MapRelated(IEnumerable<ResultsSet> all) {
var map = MapResultSet(all);
return relatedItem => map(all.First(x => x.UID == relatedItem.Item_ID));
}
static Func<ResultsSet, DTOItem> MapResultSet(IEnumerable<ResultsSet> all) {
return s =>
new DTOItem {
ID = s.ResultId.GetOrElse(0).ToString(),
UniqueId = s.UID,
Payload = s.ResultBlob,
RelatedItems = (s.RelatedSet ?? new RelatedItems[0]).Select(MapRelated(all)).ToList()
};
}
Sample usage:
var data = new[] {
new ResultsSet {
UID = 1,
RelatedSet = new[] {
new RelatedItems { Item_ID = 2 },
new RelatedItems { Item_ID = 3 },
},
},
new ResultsSet {
UID = 2,
},
new ResultsSet {
UID = 3,
},
};
var items = data.Select(MapResultSet(data)).ToList();
Debug.Assert(items.Count == 3);
Debug.Assert(items[0].UniqueId == 1);
Debug.Assert(items[1].UniqueId == 2);
Debug.Assert(items[2].UniqueId == 3);
Debug.Assert(items[0].RelatedItems.Count == 2);
Debug.Assert(items[0].RelatedItems[0].UniqueId == items[1].UniqueId);
Debug.Assert(items[0].RelatedItems[1].UniqueId == items[2].UniqueId);
I assumed Item_ID is the 'key' to UID, otherwise simply adjust MapRelated.
Generally speaking, I think AutoMapper is only useful if you have to map untyped data into typed data, and even in that case I'd think really hard before using it. Otherwise, some LINQ code is simpler and more type safe.