Automapper with nested destination Property - c#

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.

Related

How to map (using AutoMapper) a list in a many to many entity

I want to map ProjectDto object to Project object.
So, ProjectDto class contains list of styles:
public class ProjectDto
{
public List<StyleDto>? Styles { get; set; }
// and other properties...
}
And it's a Project class:
public class Project
{
public virtual IEnumerable<StyleOfProject> StylesOfProject { get; set; }
// and other properties...
}
There is many-to-many relationship between Style and Project, which is represented in StyleOfProject class:
public class StyleOfProject
{
public int ProjectId { get; set; }
public virtual Project Project { get; set; }
public int StyleId { get; set; }
public virtual Style Style { get; set; }
}
public class Style
{
public virtual IEnumerable<StyleOfProject> StyleOfProjects { get; set; }
// and other properties...
}
So, I tried to map like this:
CreateMap<ProjectDto, Project>().ForMember(dest => dest.StylesOfProject, opt => opt.MapFrom(src => src.Styles))
And I got empty StylesOfProject. I understand this is incorrect mapping way, but I don't have any right ideas how to map it.
So, I found a solution to my problem:
CreateMap<ProjectDto, Project>()
.ForMember(dest => dest.StylesOfProject,
opt => opt.MapFrom(src => src.Styles))
.AfterMap((_, dest) =>
{
foreach (var s in dest.StylesOfProject)
{
s.ProjectId = dest.Id;
s.Project = dest;
}
});
CreateMap<StyleDto, StyleOfProject>()
.ForMember(dest => dest.Style,
opt => opt.MapFrom(src => src))
.AfterMap((_, dest) =>
{
dest.StyleId = dest.Style.Id;
});
CreateMap<StyleDto, Style>();
AfterMap() is very useful thing in AutoMapper. You can find more information on this website.

Map components of DTO which are DTOs as well

This is my class which holds database data:
public partial class PermissionGroup
{
public int Id { get; set; }
public string Name { get; set; }
// other database properties
public virtual ICollection<GroupActionPermission> GroupActionPermissions { get; set; }
}
And that's my dto's:
public class PermissionGroupDTO
{
public int Id { get; set; }
public string Name { get; set; }
public ICollection<GroupActionPermissionDTO> ActionPermissions { get; set; }
}
public class GroupActionPermissionDTO
{
public int Id { get; set; }
public int GroupId { get; set; }
public int PermissionActionId { get; set; }
public PermissionGroupDTO Group { get; set; }
}
Now, I am making mapping:
public IEnumerable<PermissionGroupDTO> GetGroups()
{
return OnConnect<IEnumerable<PermissionGroupDTO>>(db =>
{
return db.PermissionGroups
.Include(i => i.GroupActionPermissions)
.ProjectTo<PermissionGroupDTO>()
.ToList();
});
}
And I am getting collection of PermissionGroupDTO which should contains collection of GroupActionPermissionDTO, but that collection stays null. Is there something wrong with my code? I am afraid that automapper can map collections from foreign keys.
Also, thats my automapper initializer:
Mapper.Initialize(cfg =>
{
cfg.CreateMap<PermissionGroup, PermissionGroupDTO>();
cfg.CreateMap<GroupActionPermission, GroupActionPermissionDTO>();
});
I believe the reason is desribed here http://docs.automapper.org/en/stable/Queryable-Extensions.html
Note that for this feature to work, all type conversions must be explicitly handled in your Mapping.
So that means you should manually configure the mapping:
Mapper.Initialize(cfg =>
{
cfg.CreateMap<PermissionGroup, PermissionGroupDTO>()
.ForMember(dto => dto.ActionPermissions , conf => conf.MapFrom(ol => ol.GroupActionPermissions )));;
cfg.CreateMap<GroupActionPermission, GroupActionPermissionDTO>();
});
BTW, note that fields are named differently: GroupActionPermissions vs. ActionPermissions. This is also the reason why automapper doesn't map it automatically and then you should use the manual configuration I wrote.

Using Automapper to map an objects list

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))
;
});

AutoMapper: Mapping a collection of Object to a collection of strings

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()));

AutoMapper ignores Properties

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

Categories