Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

AutoMapper Map to different type based on an enum?

Tags:

c#

automapper

I'm starting to implement AutoMapper, first I managed to integrate it with Castle.Windsor, which I'm already using. Now I have a Post entity which I want to map to either a LinkPostModel or an ImagePostModel. Both inherit from PostModel

1) This is what I have so far:

public class PostModelFromPostEntityConverter : ITypeConverter<Post, PostModel>
{
    private readonly IPostService postService;

    public PostModelFromPostEntityConverter(IPostService postService)
    {
        if (postService == null)
        {
            throw new ArgumentNullException("postService");
        }
        this.postService = postService;
    }

    public PostModel Convert(ResolutionContext context)
    {
        Post post = (Post)context.SourceValue;
        Link link = post.Link;
        if (link.Type == LinkType.Html)
        {
            return new LinkPostModel
            {
                Description = link.Description,
                PictureUrl = link.Picture,
                PostId = post.Id,
                PostSlug = postService.GetTitleSlug(post),
                Timestamp = post.Created,
                Title = link.Title,
                UserMessage = post.UserMessage,
                UserDisplayName = post.User.DisplayName
            };
        }
        else if (link.Type == LinkType.Image)
        {
            return new ImagePostModel
            {
                PictureUrl = link.Picture,
                PostId = post.Id,
                PostSlug = postService.GetTitleSlug(post),
                Timestamp = post.Created,
                UserMessage = post.UserMessage,
                UserDisplayName = post.User.DisplayName
            };
        }
        return null;
    }
}

Obviously the point in implementing AutoMapper is removing repeat code like this, so how am I supposed to map the common stuff, before adding my custom rules (such as the if-clause)

Ideally I'd want this to be something like:

public class PostModelFromPostEntityConverter : ITypeConverter<Post, PostModel>
{
    [...]

    public PostModel Convert(ResolutionContext context)
    {
        Post post = (Post)context.SourceValue;
        Link link = post.Link;
        if (link.Type == LinkType.Html)
        {
            return Mapper.Map<Post, LinkPostModel>(post);
            // and a few ForMember calls?
        }
        else if (link.Type == LinkType.Image)
        {
            return Mapper.Map<Post, ImagePostModel>(post);
            // and a few ForMember calls?
        }
        return null;
    }
}

2) After this mapping is complete. I have a "parent" mapping, where I need to map an IEnumerable<Post> the following model:

public class PostListModel : IHasOpenGraphMetadata
{
    public OpenGraphModel OpenGraph { get; set; } // og:model just describes the latest post
    public IList<PostModel> Posts { get; set; }
}

So basically I'd need another TypeConverter (right?), which allows me to map the posts list first, and then create the og:model

I have this, but it feels kind of clunky, I feel it could be better:

public class PostListModelFromPostEntityEnumerableConverter : ITypeConverter<IEnumerable<Post>, PostListModel>
{
    public PostListModel Convert(ResolutionContext context)
    {
        IEnumerable<Post> posts = (IEnumerable<Post>)context.SourceValue;
        PostListModel result = new PostListModel
        {
            Posts = posts.Select(Mapper.Map<Post, PostModel>).ToList()
        };
        Post first = posts.FirstOrDefault();
        result.OpenGraph = Mapper.Map<Post, OpenGraphModel>(first);
        return result;
    }
}

3) I didn't actually run the code yet, so another question comes to mind, and that is why aren't mappings strongly typed in converters?

IEnumerable<Post> posts = (IEnumerable<Post>)context.SourceValue;

where it could actually be

IEnumerable<Post> posts = context.SourceValue;
like image 967
bevacqua Avatar asked Aug 05 '12 16:08

bevacqua


People also ask

When should you not use AutoMapper?

If you have to do complex mapping behavior, it might be better to avoid using AutoMapper for that scenario. Reverse mapping can get very complicated very quickly, and unless it's very simple, you can have business logic showing up in mapping configuration.

Is AutoMapper faster than manual mapping?

Inside this article, it discusses performance and it indicates that Automapper is 7 times slower than manual mapping. This test was done on 100,000 records and I must say I was shocked.

Can AutoMapper map collections?

AutoMapper supports polymorphic arrays and collections, such that derived source/destination types are used if found.

Is it good to use AutoMapper?

AutoMapper is a great tool when used for simple conversions. When you start using more complex conversions, AutoMapper can be invaluable. For very simple conversions you could of course write your own conversion method, but why write something that somebody already has written?


1 Answers

Trying to get Necromancer badge.
Nowadays this task can be solved much easier with using ConstructUsing function specifc fields should be filled in the provided action, but all the common fields will go to ForMember execution of the mapping. Collections in this case doesn't requires any additional logic/mapping configurations. Classes that has a property of type collection as well.

cfg.CreateMap<Post, PostModel>()
    .ConstructUsing(p =>
    {
        switch (p.Type)
        {
            case LinkType.Html: return new LinkPostModel
            {
                Title = p.Description
                // other specific fields
            };
            case LinkType.Image: return new ImagePostModel
            {
                // other specific fields
            };
        }
        return null;
    })
    .ForMember(x => x.PostId, m => m.MapFrom(p => p.Id));
cfg.CreateMap<PostList, PostListModel>();
like image 90
ASpirin Avatar answered Oct 03 '22 15:10

ASpirin