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

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

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

Map two classes in runtime using column mapping information provided in a JSON file

I have two Classes like this.
public class InputModel
{
public int studentid { get; set; }
public string studentname { get; set; }
public string studentcity { get; set; }
}
public class OutputModel
{
public int StudentIDColumn { get; set; }
public string StudentNameColumn { get; set; }
public string StudentCityColumn { get; set; }
}
Now the requirement is like this:
I will receive an object of InputModel Class. From this, I need to create an object of OutputModel class.
It's simple if we use a library like AutoMapper. But the problem is, the Column-To-Column Mapping information will be supplied via a Json File like this:
{
"studentid": "StudentIDColumn",
"studentname": "StudentNameColumn",
"studentcity": "StudentCityColumn"
}
Based on the JSON mapping data, I need to Map the columns in runtime and generate the Output class object.
I tried to map the two classes using Automapper. But I am not sure how to do it in runtime using the JSON file.
var MapperConfig = new MapperConfiguration(c =>
c.CreateMap<InputCSVModel, OutputIDMModel>()
.ForMember(dest => dest.StudentIDColumn, act => act.MapFrom(src => src.studentid))
.ForMember(dest => dest.StudentNameColumn, act => act.MapFrom(src => src.studentname))
.ForMember(dest => dest.StudentCityColumn, act => act.MapFrom(src => src.studentcity))
);
var mapper = new Mapper(MapperConfig);
OutputIDMModel outModel = mapper.Map<OutputIDMModel>(inputModel);
I know that it's might be possible to do this with Reflection. But is there any better approach ?
I was able to read the JSOn file and pass the strings in the automapper config like this.
var MapperConfig = new MapperConfiguration(c =>
c.CreateMap<InputModel, OutputModel>()
.ForMember("StudentIDColumn", opt => opt.MapFrom("studentid"))
.ForMember("StudentNameColumn", opt => opt.MapFrom("studentname"))
.ForMember("StudentCityColumn", opt => opt.MapFrom("studentcity"))
);

Automapper: mapping between different types

Assuming I have a class structure like
public class Entity
{
public List<EntityChild> Children { get; set; }
}
public class EntityChild
{
public int Id { get; set; }
public string Name { get; set; }
}
and I want to map Entity using AutoMapper to a class EntityDto and reverse.
public class EntityDto
{
public List<int> EntityChildrenIds { get; set; }
}
I don't have any clue how to configure AutoMapper to map this properly in both directions. I know my Name property will be null when mapping from EntityDto to Entity but this would not be a problem.
For mapping both ways this configuration works for me:
var mapperConfiguration = new MapperConfiguration(cfg =>
{
cfg.CreateMap<Entity, EntityDto>()
.ForMember(dest => dest.EntityChildrenIds, opt => opt.MapFrom(src => src.Children))
.ReverseMap();
cfg.CreateMap<EntityChild, int>().ConvertUsing(child => child.Id);
cfg.CreateMap<int, EntityChild>().ConvertUsing(id => new EntityChild
{
Id = id
});
});
Since the properties have different names we need to configure that mapping.
Then just add general mappings from EntityChild to int and back again and we're done.
if .ReverseMap(), as mentioned by #knoop, didn't work maybe you should map it manually:
CreateMap<Entity, EntityDto>(MemberList.None)
.ForMember(dest => dest.EntityChildrenIds, opts => opts.MapFrom(src => MapChildrenIds(src.Children)));
CreateMap<EntityDto, Entity>(MemberList.None)
.ForMember(dest => dest.Children, opts => opts.MapFrom(src => MapChildren(src.EntityChildrenIds)));
private List<EntityChild> MapChildren(List<int> entityChildrenIds)
{
var listEntityChild = new List<EntityChild>();
foreach (var childId in entityChildrenIds)
listEntityChild.Add(new EntityChild { Id = childId });
return listEntityChild;
}
private List<int> MapChildrenIds(List<EntityChild> children)
{
return children.Select(x => x.Id).ToList();
}

How to provide additional parameter to Mapper's Map method?

I'm trying to map one list to another, but not everything I want is in the source object. Let me explain:
These are my two objects:
public class SourceItem
{
public int Id { get; set;}
public string Name { get; set;}
}
public class TargetItem
{
public int TargetId { get; set;}
public string TargetName { get; set;}
public int Foo { get; set; }
}
This is how I configure my Mapper:
public class MyProfile : Profile
{
public MyProfile()
{
CreateMap<SourceItem, TargetItem>()
.ForMember(dest => dest.TargetId, opt => opt.MapFrom(src => src.Id))
.ForMember(dest => dest.TargetName, opt => opt.MapFrom(src => src.Name));
}
}
When I do this...
var targets = Mapper.Map<List<SourceItem>, List<TargetItem>>(sourceItems);
...the targets list's objects will have Foo set to 0. Obviously. What I'm trying to do is to also initialize Foo (with the parameter). Like this:
fooParameter = 321;
List<TargetItem> targets = Mapper.Map<List<SourceItem>, List<TargetItem>>(sourceItems);
foreach(var target in targets)
{
target.Foo = fooParameter;
}
Is it possible, to do it using Mapper?
Thanks in advance!
You can't do exactly what you want, but you can get pretty close by specifying mapping options when you call Map.
fooParameter = 321;
var targets = Mapper.Map<List<SourceItem>, List<TargetItem>>(sourceItems, opt =>
opt.AfterMap(
(src, dest) => {
foreach (var d in dest)
{
d.Foo = fooParameter;
}
})
);

Map just one field with Automapper

I'm using Automapper to define a mapping configuration between some objects. I need to retrieve single mapped values for the destination based on the configuration.
Let's say I have a domain object
class Person
{
public string FirstName { get; set; }
public string LastName { get; set; }
}
and a corresponding DTO
class PersonDTO
{
public string FullName { get; set; }
}
and a mapping
Mapper.CreateMap<Person, PersonDTO>()
.ForMember(
dest => dest.FullName,
opts => opts.MapFrom(src => src.FirstName + " " + src.LastName));
I'd like a method like Mapper.MapField<PersonDTO>(person, x => x.FullName) that returns just one mapped value (that would get assigned to the specified field in a full mapping). Is there any way I can do this without mapping the whole object and extracting the value from the result?
First create a resolver and then use the resolve using method in your mapper call.
public class PersonNameResolver : ValueResolver<Person, PersonDTO>
{
protected override PersonDTO ResolveCore(Person source)
{
return new PersonDTO
{
FirstName = source.FirstName,
LastName = source.LastName
};
}
}
Once this is created, use the following in your mapper call.
Mapper.CreateMap<Person, PersonDTO>()
.ForMember(
dest => dest.Name,
opts => opts.ResolveUsing<PersonNameResolver>().FromMember(o => o))

Need help with AutoMapper

I have two classes that looks as follows:
public class Rule
{
public int Id { get; set; }
public RuleGroup RuleGroup { get; set; }
}
public class RuleGroup
{
public int Id { get; set; }
public List<Rule> RuleList { get; set; }
}
A RuleGroup has a list of rules. My AutoMapper settings are as follows:
Mapper.CreateMap<RuleRecord, FirstSolar.Mes.Core.Entities.Recipe.Rule>()
.ForMember(destination => destination.RuleGroup, source => source.Ignore())
.ForMember(destination => destination.Id, source => source.MapFrom(item => item.RuleId));
Mapper.CreateMap<IList<RuleRecord>, IList<FirstSolar.Mes.Core.Entities.Recipe.Rule>>();
Mapper.CreateMap<RuleGroupRecord, FirstSolar.Mes.Core.Entities.Recipe.RuleGroup>()
.ForMember(destination => destination.Id, source => source.MapFrom(item => item.RuleGroupId));
Mapper.CreateMap<IList<RuleGroupRecord>, IList<FirstSolar.Mes.Core.Entities.Recipe.RuleGroup>>();
When I attempt to map a RuleGroupRecord (LinqToSQL object) to RuleGroup (DTO), AutoMapper says I need to add a mapping for RuleGroup.RuleList. I'm wondering why because I defined how to map a single RuleRecord and a List.
If I have to, how would I do it?
Simply add (I hope I got the syntax right, but you should see what I'm hinting at):
.ForMember(destination => destination.RuleList, source => source.MapFrom(item => item.Rules));
to the second mapping. While you handled the general mapping for RuleRecord to Rule in the first mapping, you didn't tell automapper to map the specific property RuleGroup.RuleList.

Categories

Resources