I need help with a special mapping with AutoMapper. I want to map a collection of objects to a collection of strings.
So I have a Tag class
public class Tag
{
public Guid Id { get; set; }
public string Name {get; set; }
}
Than in a model I have a IList of this class. Now I want to map the name's to a collection of strings.
Thats how I define the mapping rule:
.ForMember(dest => dest.Tags, opt => opt.ResolveUsing<TagNameResolver>())
And here is my ValueResolver:
protected override string ResolveCore(Tag source)
{
return source.Name;
}
But you know.. it doesn't work ;-) So maybe someone know how to do it right and can help me.
thanks a lot
Update to Jan
Sooo.. you wanted more details.. here you got it.. but I have shorten it ;)
So the Model:
public class Artocle
{
public Guid Id { get; set; }
public string Title {get; set; }
public string Text { get; set; }
public IList<Tag> Tags { get; set; }
}
And the Tag model you can see above.
I want to map it to a ArticleView... I need the tag model only for some business context, not for the output.
So here is the ViewModel I need to map to:
public class ArticleView
{
public Guid Id { get; set; }
public string Title { get; set; }
public string Text { get; set; }
public IList<string> Tags { get; set; } // The mapping problem :-)
}
So I have a BootStrapper for the mappings. My Mapping looks like this:
Mapper.CreateMap<Article, ArticleView>()
.ForMember(dest => dest.Tags, opt => opt.ResolveUsing<TagNameResolver>())
And I map it manuelly with a special method
public static ArticleView ConvertToArticleView(this Article article)
{
return Mapper.Map<Article, ArticleView>(article);
}
A unit test validated the following would map from IList<Tag> to IList<string>
private class TagNameResolver : ValueResolver<IList<Tag>, IList<string>>
{
protected override IList<string> ResolveCore(IList<Tag> source)
{
var tags = new List<string>();
foreach (var tag in source)
{
tags.Add(tag.Name);
}
return tags;
}
}
This is a shorter way of creating the map:
.ForMember(dest => dest.Tags, opt => opt.MapFrom(so => so.Tags.Select(t=>t.Name).ToList()));
Related
my first StackOverflow question so please bear with me, and thank you for your help in advance! :)
How on earth can I get my asp.net core controller to respond with the DTO of the child collection within the DTO of the parent response? I need two separate DTOs because of some business logic constraints that call for this many-to-many relationship situation.
Tried automapper and spent the last two days of my life researching this to no avail.
I tried the following in my Controller but always get an empty child collection. I can get the child collection to display if I return the entity class which is not great with many to many relationships.
I want to end up with JSON that looks like...
[
{ prop : ..,
prop: ..,
collection[
{
prop:..,
prop:..
}
]
}
]
This is what I have in my controller:
public ActionResult<IEnumerable<LogEntryDto>> GetAllEntries()
{
var entryList = _context.Entries.ToList();
return Ok(_mapper.Map<IEnumerable<RiskGetDto>>(entryList));
}
My Automapper profile classes contains simple mapping between the entities and DTOs
CreateMap<LogEntry, LogEntryDto>();
CreateMap<Tag, TagDto>();
I have a the following class
public class LogEntry
{
public int Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public List<Tag> Tags { get; set; } = new List<Tag>();
}
And another
public class Tag
{
public int Id { get; set; }
public string Name { get; set; }
public string Value { get; set; }
public List<LogEntry> LogEntries{ get; set; } = new List<LogEntry>();
}
And the following DTOs for each class
public class LogEntryDto
{
public string Name { get; set; }
public string Description { get; set; }
public List<Tag> Tags { get; set; } = new List<Tag>();
}
and...
public class TagDto
{
public string Name { get; set; }
public string Value { get; set; }
}
Thank you for your help.
Ok, just in case someone will be helped by this fix. My silly mistake was driven by my lack of lambda expression mileage(!!);
I needed to retrieve the full set of properties in the GetAllEntries() method in the service class like so (notice the ToList() on the Tags collection):
public List<Risk> GetAllEntries()
{
var entryList = _context.Entries.Select(e=> new Risk
{
Id = e.Id,
RiskName = e.RiskName,
RiskDescription = e.RiskDescription,
---->>> **Tags = e.Tags.ToList()**
}).ToList();
return entryList;
}
This is then passed to the controller and mapped like so:
[HttpGet]
public ActionResult<List<LogEntryDto>> GetAllEntries()
{
var entries = _entryService.GetAllEntries();
return Ok(_mapper.Map<List<LogEntryDto>>(entries));
}
And then let AutoMapper do its magic with the simple mapping profile...:
public EntriesProfile()
{
CreateMap<LogEntry, LogEntryDto>()
.ForMember
(dto => dto.EntryId,
dbEntity => dbEntity.MapFrom(src => ....)
.ReverseMap();
CreateMap<Tag, TagDto>()
.ForMember
(dto => dto.Name,
dbEntity => dbEntity.MapFrom(src => src.Name))
.ForMember
(dto => dto.Value,
dbEntity => dbEntity.MapFrom(src => src.Value))
.ReverseMap();
}
I have an object (ProductModel) that has a nested list of images. I am trying to simplify the model (Product) that has this list as its property. I am using Automapper, but I can not seem to get the mapping configuration right. I viewed several other posts, but they seem to be a little different than what I am trying to achieve.
// Map to:
public class Product
{
public List<Image> Images { get; set; }
}
public class Image
{
public string url { get; set; }
}
// Map from:
public class ProductModel
{
public ImageSet ImageSet { get; set; }
}
public class ImageSet
{
public List<ImageDetail> ImageDetails { get; set; }
}
public class ImageDetail
{
public string Url { get; set; }
}
The following configuration should work:
var config = new MapperConfiguration(cfg =>
{
cfg.CreateMap<ImageDetail, Image>();
cfg.CreateMap<ProductModel, Product>()
.ForMember(dest => dest.Images, opt => opt.MapFrom(src => src.ImageSet.ImageDetails))
;
});
I am trying to map a nested child property like so.
var mapperConfig = new MapperConfiguration(cfg =>
{
cfg.CreateMap<Collection, CollectionDTO>()
.ForMember(dest => dest.Items.Select(x => x.AddedToCollectionDate),
opts => opts.MapFrom(src =>
src.CollectionItems.Select(ci => ci.AddedToCollectionDate)));
});
Collection.Items is a List<Item>. Each Item has a AddedToCollectionDate property that I need to populate from the source mapping.
CollectionDTO has a navigational property to a cross-join table called CollectionItem, which has a property called AddedToCollectionDate.
Error:
Custom configuration for members is only supported for top-level individual members on a type.
How can I achieve this with AutoMapper?
Clases (omitted other properties for brevity):
public partial class Collection
{
public virtual ICollection<CollectionItem> CollectionItems { get; set; }
}
public partial class CollectionItem
{
public System.DateTime AddedToCollectionDate { get; set; }
public virtual Collection Collection { get; set; }
public virtual Item Item { get; set; }
}
public class CollectionDTO
{
public List<ItemDTO> Items { get; set; }
public DateTime LastAccessedDate { get; set; }
}
public class Item
{
public DateTime LastAccessedDate { get; set; }
public virtual ICollection<CollectionItem> CollectionItems { get; set; }
}
I got it to work by doing a FirstOrDefault() instead of aSelect() selection like this.
cfg.CreateMap<Item, ItemDTO>()
.ForMember(dest => dest.AddedToCollectionDate,
opts => opts.MapFrom(src =>
src.CollectionItems.FirstOrDefault().AddedToCollectionDate));
Instead of the ForMember call, you should configure a mapping for CollectionItem as well.
cfg.CreateMap<CollectionItem, ItemDTO>();
cfg.CreateMap<Collection, CollectionDTO>();
If you also rename CollectionDTO.Items to CollectionDTO.CollectionItems AutoMapper knows enough to map Collection.CollectionItems to the right collection in CollectionDTO.
I'm struggling using AutoMapper to map these objects.
Here are my DTO:
public class ContainerDTO
{
public List<MoneyAccountDTO> MoneyAccounts { get; set; }
public List<CardDTO> Cards { get; set; }
}
public class MoneyAccountDTO
{
public string Iban { get; set; }
}
public class CardDTO
{
public string MoneyAccountIban { get; set; }
}
Here are the BusinessObject:
public class Container
{
public List<MoneyAccount> MoneyAccounts { get; set; }
}
public class MoneyAccount
{
public string Iban { get; set; }
public List<Card> Cards { get; set; }
}
public class Card
{
public string MoneyAccountIban { get; set; }
}
What i try to achieve here, is to find all CardDTO in the ContainerDTO that have the same Iban than a MoneyAccount and Create a list of these cards in the MoneyAccount itself.
The probleme here is that i receive the CardsDTO object at the same level as the MoneyAccountDTO it related to.
I started with something like that:
cfg.CreateMap<ContainerDTO, Container>()
.ForMember(dest => dest.MoneyAccounts, opt => opt.MapFrom(src => src.Cards.
Where(c => c.MoneyAccountIban == XXX)));
But i'm unable to replace those XXX by the Money account the mapper is enumerating. It think i'm not in the right direction.
I'm unable to find a good solution for this mapping.
Any Help will be appreciated.
The solution for this problem is to use a foreach with a where clause.
cfg.CreateMap<ContainerDTO, Container>().AfterMap((src, dest) => dest.MoneyAccounts
.ForEach(a => a.Cards = src.Cards
.Where(c => c.MoneyAccountIban == a.Iban)
.Select(d => Mapper.Map<CardDTO, ICard>(d)).ToList()));
It works like a charm !
when I'm using this mapping
Mapper.CreateMap<DataSourceConfigurationContract, DataSourceConfigurationContract>().ForMember(x => (object)x.DatabaseTypeException, opt => opt.Ignore())
.ForMember(x => (object)x.DatabaseType, opt => opt.Ignore());
var mappedValue = Mapper.Map<DataSourceConfigurationContract, DataSourceConfigurationContract>(dataSourceConfiguration);
for this class
public sealed class DataSourceConfigurationContract {
public string Name { get; set; }
public string ConnectionString { get; set; }
public string ConnectionType { get; set; }
public DataSourcePropertyContractCollection Properties { get; set; }
public DataSourceAreaConfigurationContractCollection Areas { get; set; }
public UserContractCollection AllowedUsers{ get; set; }
public DataSourceType? DatabaseType { get; set; }
public ExceptionContract DatabaseTypeException { get; set; }
public DataSourceType DataSourceType { get; set; } }
some Properties are ignored (e.g. Areas) that should be mapped. The string properties seem to be always correctly mapped. What have I done wrong?
AutoMapper only support the following collections out of the box: http://automapper.codeplex.com/wikipage?title=Lists%20and%20Arrays&referringTitle=Home . I guess that your properties that are not copied are of type XXXCollection.
You can solve this by creating a custom type converter for your collection types: http://automapper.codeplex.com/wikipage?title=Custom%20Type%20Converters&referringTitle=Home
For your collections you need to do something similar to the following (taken from some code I've recently worked on):
Mapper.CreateMap<List<QuizItemTypeModel>, List<Quiz.DataContracts.QuizItemType>>()
.Include<QuizDataCompositeModel, Quiz.DataContracts.QuizDataComposite>();
Where QuizDataCompositeModel and Quiz.DataContracts.QuizDataComposite both extend List<"RespectiveType">
It's quite simple:
Mapper.CreateMap<DataSourceAreaConfigurationContract, DataSourceAreaConfiguration>();
Mapper.CreateMap<DataSourceConfigurationContract, DataSourceConfigurationContract>()
.ForMember(dest => dest.Areas, opt => opt.UseDestinationValue());
Tipp: Download the source code and learn from the given unittests and samples!
You can get it there: http://automapper.codeplex.com/SourceControl/list/changesets