How to use automapper with different properties names? [duplicate] - c#

I am a newbie to the Automapper framework. I have a domain class and a DTO class as follows:
public class Employee
{
public long Id {get;set;}
public string Name {get;set;}
public string Phone {get;set;}
public string Fax {get;set;}
public DateTime DateOfBirth {get;set;}
}
public class EmployeeDto
{
public long Id {get;set;}
public string FullName {get;set;}
public DateTime DateOfBirth {get;set;}
}
Note: The name of property "Name" of Employee class is not the same as that of property "FullName" of EmployeeDto class.
And here's the code to map the Employee object to EmployeeDto:
Mapper.CreateMap<Employee, EmployeeDto>(); // code line (***)
EmployeeDto dto = Mapper.Map<Employee, EmployeeDto>(employee);
My question is: If I want to map Employee (source class) to EmployeeDto (destination class), how can I specify the mapping rule? In other words, how should I do more with code line (***) above?

Never mind, I myself found a solution:
Mapper.CreateMap<Employee, EmployeeDto>()
.ForMember(dest => dest.FullName, opt => opt.MapFrom(src => src.Name));

Just to roll the comments above into an updated approach using Automapper 8.1+...
var mapConfig = new MapperConfiguration(
cfg => cfg.CreateMap<Employee, EmployeeDto>()
.ForMember(dest => dest.FullName, opt => opt.MapFrom(src => src.Name))
);
Then you would build the mapper using the mapConfig:
var mapper = mapConfig.CreateMapper();

We can also specify on Class attributes for mapping
From https://docs.automapper.org/en/stable/Conventions.html#attribute-support
Attribute Support
AddMemberConfiguration().AddName<SourceToDestinationNameMapperAttributesMember>();
* Currently is always on
Looks for instances of SourceToDestinationMapperAttribute for
Properties/Fields and calls user defined isMatch function to find
member matches.
MapToAttribute is one of them which will match the property based on
name provided.
public class Foo
{
[MapTo("SourceOfBar")]
public int Bar { get; set; }
}

Considering that we have two classes
public class LookupDetailsBO
{
public int ID { get; set; }
public string Description { get; set; }
}
and the other class is
public class MaterialBO
{
[MapTo(nameof(LookupDetailsBO.ID))]
public int MaterialId { get; set; }
[MapTo(nameof(LookupDetailsBO.Description))]
public string MaterialName { get; set; }
public int LanguageId { get; set; }
}
In this way you know typically to which property you follow .
and you make sure of the naming convention , so if you have changed the propery name in the source . The MapTo() will prompt an error

The new style of Attribute Mapping via Data Annotations:
https://docs.automapper.org/en/v8.1.0/Attribute-mapping.html?highlight=annotation
[AutoMap(typeof(Order))]
public class OrderDto {
// This is equivalent to a CreateMap<Order, OrderDto>()
For mapping the member
[SourceMember(nameof(Order.OrderTotal))]
public decimal Total { get; set; }
If you want reverse map then you add that property in
[AutoMap(typeof(Order), ReverseMap = true )]
public class OrderDto {
// This is equivalent to a CreateMap<Order, OrderDto>().ReverseMap()

The above answers are great and hope OP has got his answer. I just want to add how we can map fixed values instead of fields using UseValue() method of IMemberConfigurationExpression Interface.
Mapper.CreateMap<Employee, EmployeeDto>()
.ForMember(dest => dest.Department, opt => opt.UseValue("Development"));
This will map "Development" as a Department property value for destination data.

Related

Unable to map from one ViewModel to another

I'm making a list of checkboxes to update a user's roles, and I'm trying to map from this:
public class ApplicationRoleViewModel
{
public Guid Id { get; set; }
public string Name { get; set; }
public string NormalizedName { get; set; }
public string ConcurrencyStamp { get; set; }
public int SortOrder { get; set; }
public string DisplayName { get; set; }
public string Description { get; set; }
public string Icon { get; set; } // Font Awesome-ikoner, f.eks. "fa-user"
}
to this:
public class SelectableRoleViewModel
{
public Guid Id { get; set; }
public string DisplayName { get; set; }
public bool Selected { get; set; }
}
This is my mapping:
CreateMap<ApplicationRoleViewModel, SelectableRoleViewModel>()
.ForMember(dest => dest.Id, s => s.MapFrom(i => i.Id))
.ForMember(dest => dest.DisplayName, s => s.MapFrom(d => d.DisplayName))
.ForMember(dest => dest.Selected, i => i.Ignore());
Mapping it like this in the controller:
ApplicationRole role = await db.Roles.FirstOrDefaultAsync();
SelectableRoleViewModel sr = auto.Map<SelectableRoleViewModel>(role);
gives me the following error message:
AutoMapperMappingException: Missing type map configuration or unsupported mapping.
I am registering AutoMapper in Startup.cs like this:
services.AddAutoMapper(typeof(Startup));
Then, in AutoMapperProfile.cs:
public class AutomapperProfile : Profile
{
public AutomapperProfile()
{
// This is not working:
CreateMap<ApplicationRoleViewModel, SelectableRoleViewModel>()
.ForMember(dest => dest.Selected, i => i.Ignore());
// This is working:
CreateMap<ApplicationUser, ApplicationUserViewModel>();
// Many more mappings, all working
}
}
How can I get it to work?
The code you specified seems to be correct.
I will just suggest to remove the ForMember method for properties with the same names as auto mapper handles it automatically:
CreateMap<ApplicationRoleViewModel, SelectableRoleViewModel>()
.ForMember(dest => dest.Selected, i => i.Ignore());
The problem seems to be because you are not using the mapper right. Where have you registered the mapper? Is the registration happens before the map? Did you do it in the Startup? If you specify more code, it will be easier to help.
UPDATE:
After getting more code & info, the problem was that the map worked on a different object, ApplicationRoleViewModel and not ApplicationRole.
Just to see a difference ;)
public static SelectableRoleViewModel ToSelectable(this ApplicationRoleViewModel model)
{
return new SelectableRoleViewModel
{
Id = model.Id,
DisplayName = model.DisplayName
};
}
// Usage
var selectable = applicationRole.ToSelectable();
Type it once
Perfectly testable
Fully maintainable - supports all kinds of conversion/mapping
Reduce amount of injected dependencies and abstractions (mapper)
No extra dependencies on third party libraries

Mapping one model to another

I have two models, one of which I process and return it to a view and then from the view I send it to a controller. In the controller, I need to send it to a stored procedure but the stored procedure expects a model with different property names.
Here is my model:
public class Operator
{
public int OPERATOR_OBJECTID { get; set; }
public string SETTLEMENT_OBJECTID { get; set; }
public string TECHNOLOGY_OBJECTID { get; set; }
}
and here is the model the stored procedure expects
public class UploadModel
{
public int OPERATOR_OBJECTID { get; set; }
public string SETTLEMENT_CODE { get; set; }
public string TECHNOLOGY_CODE { get; set; }
}
Since I send the properties from Operator, like SETTLEMENT_OBJECTID but it expects SETTLEMENT_CODE it throws an exception. Can I somehow map the properties from one model to another or can I cast one model to another? What would be a good solution here?
As mentioned in the comments, you can use the automapper library and configure as such:
var mapConfig = new MapperConfiguration(
cfg => cfg.CreateMap<Operator, UploadModel>()
.ForMember(dest => dest.SETTLEMENT_CODE, opt => opt.MapFrom(src => src.SETTLEMENT_OBJECTID))
.ForMember(dest => dest.TECHNOLOGY_CODE, opt => opt.MapFrom(src => src.TECHNOLOGY_OBJECTID))
);
Check here the getting started guide: https://docs.automapper.org/en/stable/Getting-started.html
You can also define an explicit operator to be able to cast from one class to the other: https://www.dotnetperls.com/explicit

AutoMapper from list in DTO to individual object

I have the DTO below in which I need to map it to a flat view model, the idea is that some of the properties that come through from the request are shared, but there could be a list of names that come through.
public class ShinyDTO
{
public List<UserDetails> Users { get; set; }
public string SharedPropertyOne { get; set; }
public string SharedPropertyTwo { get; set; }
}
public class UserDetails
{
public string Title { get; set; }
public string Forename { get; set; }
public string Surname { get; set; }
}
public class MyRealClass
{
public string SharedPropertyOne {get;set;}
public string SharedPropertyTwo {get;set;}
public string Title {get;set;}
public string Forename {get;set;}
public string Surname {get;set;}
}
//This will map all the shared properties
MyRealClass request = Mapper.Map<MyRealClass>(dto);
foreach (var record in dto.Users){
//This bit overwrites the properties set above and then I only have the properties set for Forename, Surname, etc...
request = Mapper.Map<MyRealClass>(record);
}
I need to map this into a list of MyRealClass. I've tried creating seperate mappings and then looping it within a foreach, but this kept removing the initial attributes.
I've also tried setting up the second mapping to ignore the properties set above and I couldn't get this working, it was still overwriting the properties.
var autoMapperConfiguration = new MapperConfigurationExpression();
autoMapperConfiguration
.CreateMap<MyRealClass, UserDetails>()
.ForMember(c => c.SharedPropertyOne, d => d.Ignore())
.ForMember(c => c.SharedPropertyTwo, d => d.Ignore());
I think you're close, but your question states:
I need to map this into a list of MyRealClass
... and your attempted mapping maps MyRealClass to UserDetails. It seems like you actually want a map from UserDetails to MyRealClass instead.
Anyway, here's one way to accomplish this:
var autoMapperConfiguration = new MapperConfigurationExpression();
autoMapperConfiguration.CreateMap<UserDetails, MyRealClass>();
autoMapperConfiguration.CreateMap<ShinyDTO, MyRealClass>();
var results = new List<MyRealClass>();
foreach(var record in dto.Users) {
var mapped = Mapper.Map<MyRealClass>(dto);
Mapper.Map(record, mapped);
results.Add(mapped);
}
Here, the second Mapper.Map call maps record onto mapped, and it should not overwrite the values that have already been mapped over by the mapping from ShinyDTO to MyRealClass.
You could also get fancy and do all of this in a ConstructUsing call, but this seems clearer to me.
You can create a map between UserDetails and IEnumerable<MyRealClass>.
var autoMapperConfiguration = new MapperConfigurationExpression();
autoMapperConfiguration
.CreateMap<IEnumerable<MyRealClass>, UserDetails>()
.ForMember(dest => dest.SharedPropertyOne, opt => opt.MapFrom(x => x.get(0).SharedPropertyOne)); //you can check if the list is empty
.ForMember(dest => dest.SharedPropertyTwo, opt => opt.MapFrom(x => x.get(0).SharedPropertyTwo)); //you can check if the list is empty
.AfterMap((src,dest) => //src is a list type
{
foreach(MyRealClass myrealclass in src)
dest.Users.add(new UserDetails(){
Title = myrealclass.Title,
Forename = myrealclass.Forename,
Surname = myrealclass.Surname
});
});

Using Automapper with Flags Enum

I'm trying to figure out how to use AutoMapper with the following scenario :-
I have the following Entity Model :-
public class Lender : LegacyEntity
{
public int Id { get; set; }
public string Name { get; set; }
public ClaimType ClaimTypes { get; set; }
//Other properties kepy out for brevity.
}
And Here is The Following Service Model :-
public class LenderServiceModel
{
[Editable(false)]
public int Id { get; set; }
[Editable(false)]
public string Name { get; set; }
[Editable(false)]
public List<string> ClaimTypes { get; set; }
}
In the case of the Entity model, the ClaimType property is a Flags Enum :-
[Flags]
public enum ClaimType : int
{
A = 1,
B = 2,
C = 4,
}
I want to be able to map from the Entity Model, to the Service Model. I need to map the ClaimType to List on the Service Model, but i have had no luck.
I'm new to AutoMapper, any help would be apreciated.
You need to create a property mapping for ClaimTypes, which converts each flags value to a string. There are a few ways to do this. Take a look at this answer for some ideas.
Here's how you can set it up in AutoMapper (using a quick and dirty method of just ToString()ing the enum then splitting the string):
Mapper.CreateMap<Lender, LenderServiceModel>()
.ForMember(dest => dest.ClaimTypes, opts => opts.MapFrom(src =>
src.ClaimTypes.ToString().Split(new string[]{", "}, StringSplitOptions.None).ToList()));
You can see a working .NETFiddle here
First you need to get hold of a string list representation of your enum flags, this can be done with this statement
var t = Enum.GetValues(typeof(ClaimType)).Cast<ClaimType>().Where(r => (claimType & r) == r).Select(r => r.ToString()).ToList();
For specific mappings with AutoMapper you need to specify it while setting it up.
So for this that will be following code: so we map ClaimTypes field from source to destination using the list conversion...
AutoMapper.Mapper.CreateMap<LegacyEntity, LenderServiceModel >()
.ForMember(dest => dest.ClaimTypes,
opts => opts.MapFrom(Enum.GetValues(typeof(ClaimType)).Cast<ClaimType>().Where(r => (src.ClaimTypes & r) == r).Select(r => r.ToString()).ToList());

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