How to map from a tuple without specifying each member field?

Viewed 449

Given the following sample classes

public class User
{
    public string Username { get; set; }
    public string FieldFromUser { get; set; }
}

public class Todo
{
    public string Title { get; set; } // !! map this one to UsernameWithTodoTitle.TodoTitle !!
    public string FieldFromTodo { get; set; }
}

public class UsernameWithTodoTitle
{
    public string Username { get; set; }
    public string TodoTitle { get; set; } // !! this field represents Todo.Title !!
    public string FieldFromUser { get; set; }
    public string FieldFromTodo { get; set; }
}

I want to map a user and a todo to UsernameWithTodoTitle. To solve this I'm using the packages

AutoMapper v10.1.1

AutoMapper.Extensions.Microsoft.DependencyInjection v8.1.1

When dealing with multiple sources this solution works fine for me:

public class UsernameWithTodoTitleMappingProfile : Profile
{
    public UsernameWithTodoTitleMappingProfile()
    {
        CreateMap<(User, Todo), UsernameWithTodoTitle>()
            .ForMember(
                destination => destination.Username,
                memberOptions => memberOptions.MapFrom(source => source.Item1.Username))
            .ForMember(
                destination => destination.TodoTitle,
                memberOptions => memberOptions.MapFrom(source => source.Item2.Title))
            .ForMember(
                destination => destination.FieldFromUser,
                memberOptions => memberOptions.MapFrom(source => source.Item1.FieldFromUser))
            .ForMember(
                destination => destination.FieldFromTodo,
                memberOptions => memberOptions.MapFrom(source => source.Item2.FieldFromTodo));
    }
}

So I could map a user and a todo like so

var usernameWithTodoTitle = _mapper.Map<UsernameWithTodoTitle>((user, todo));

Since I'm using a tuple I have to specify each field because obviously Automapper can't know where to search for these fields when dealing with a tuple. The only different field names are Todo.Title and UsernameWithTodoTitle.TodoTitle. I would like to know if I can simplify the mapping profile, maybe I don't even have to use a tuple? The following profile is just a pseudo implementation what I would like to achieve

public class UsernameWithTodoTitleMappingProfile : Profile
{
    public UsernameWithTodoTitleMappingProfile()
    {
        CreateMap<(User, Todo), UsernameWithTodoTitle>()
            // map all fields by name as expected and ...
            .ForMember(
                destination => destination.TodoTitle,
                memberOptions => memberOptions.MapFrom(source => source.Item2.Title));
    }
}
1 Answers

As you already mention, you don't need to use a Tuple - see the update below if you really do.
You can map a single target object from multiple sources.

Set up regular mappings from your sources User and Todo to the UsernameWithTodoTitle target.

CreateMap<User, UsernameWithTodoTitle>()

CreateMap<Todo, UsernameWithTodoTitle>()
    .ForMember(
        destination => destination.TodoTitle,
        memberOptions => memberOptions.MapFrom(source => source.Title)
    );

To instantiate an UsernameWithTodoTitle object, apply one of both mappings; here from User.

var usernameWithTodoTitle = _mapper.Map<UsernameWithTodoTitle>(user);

Secondly, you update the newly created UsernameWithTodoTitle by applying the next mapping; here from Todo.

_mapper.Map(todo, usernameWithTodoTitle);

UPDATE

If you really want/need that Tuple mapping, such one can be set up as below, via an IncludeMembers rule upon the Tuple parts which will trigger the corresponding mappings.
This makes that the separate mapping rules from above are still required.

CreateMap<(User, Todo), UsernameWithTodoTitle>()
    .IncludeMembers(o => o.Item1, o => o.Item2);

You might prefer named Tuple parts.

CreateMap<(User User, Todo Todo), UsernameWithTodoTitle>()
    .IncludeMembers(o => o.User, o => o.Todo);

Complete example:

CreateMap<User, UsernameWithTodoTitle>();

CreateMap<Todo, UsernameWithTodoTitle>()
    .ForMember(
        destination => destination.TodoTitle,
        memberOptions => memberOptions.MapFrom(source => source.Title)
    );          

CreateMap<(User User, Todo Todo), UsernameWithTodoTitle>()
    .IncludeMembers(o => o.User, o => o.Todo);

var user = new User { Username = "alfki" };
var todo = new Todo { Title = "work" };
var usernameWithTodoTitle = _mapper.Map<UsernameWithTodoTitle>((user, todo));

Console.WriteLine(usernameWithTodoTitle.Username); // alfki
Console.WriteLine(usernameWithTodoTitle.TodoTitle); // work
Related