Need help with AutoMapper - c#

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.

Related

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 before/after map callbacks during mapping

I'm using ASP.NET Core and Automapper 6.1.0 ,
I have two types that look like this
public class ExampleDTO
{
public Guid Id { get; set; }
public ProviderDTO Provider { get; set; }
}
public class Example
{
public Guid Id { get; set; }
public Guid Provider { get; set; }
}
ProviderDTO class (which is irelevant in this case)
public class ProviderDTO
{
public Guid Id { get; set; }
public string Name { get; set; }
}
AutoMapper configuration looks like this:
CreateMap<Example, ExampleDTO>().ForMember(x => x.Provider, opt => opt.Ignore());
CreateMap<ExampleDTO, Example>().ForMember(dest => dest.Provider,
opt => opt.MapFrom(src => src.Provider.Id));
When I map from Example to ExampleDTO, I want to pass the value for ProviderDTO type.
I tried something like this.
_mapper.Map<ExampleDTO>(example, opt => opt.AfterMap((src, dest) => dest.Provider = myProvider));
I get this
'object' does not contain a defenition for 'Provider' and no extension method
Is this achievable? If yes, what am I doing wrong?
With the AutoMapper, you may need to provide both the source and destination type, such as:
_mapper.Map<Example, ExampleDTO>(example, opt => {
opt.AfterMap((src, dest) => dest.Provider = myProvider))
});

Automapper map int property values from List<type> to List<int>

I need to map an int property from a list of objects to a List<int>.
Here is what my class structure looks like:
I have a parent class:
public class Parent
{
...
public List<Location> Locations { get; set; }
}
Location class:
public class Location
{
public int LocationId { get; set; }
public string Name { get; set; }
}
Destination class for mapping:
public class Destination
{
...
public List<int> Locations { get; set; }
}
Here is the code I am trying to use to accomplish the mapping between List<Location> to List<int>:
CreateMap<Parent, Destination>()
.ForMember(d => d.Locations, o => o.MapFrom(s => s.Locations.Select(l => l.LocationId)))
This isn't working. I get the following error:
AutoMapper.AutoMapperMappingException: Unable to create a map expression from Location.LocationId
(System.Collections.Generic.IEnumerable`1[System.Int32]) to Destination.Locations (System.Collections.Generic.List`1[System.Int32])
Any idea what I'm not doing right?
You need to alter your AutoMapper configuration to map between Location and int then it takes care of the rest for you:
cfg.CreateMap<Location, int>().ConvertUsing(source => source.LocationId);
cfg.CreateMap<Parent, Destination>().ForMember(dest => dest.Locations, opts => opts.MapFrom(src => src.Locations));
See this Gist for working example.
As the exception says:
AutoMapper.AutoMapperMappingException: Unable to create a map expression from Location.LocationId (System.Collections.Generic.IEnumerable1[System.Int32]) to Destination.Locations (System.Collections.Generic.List1[System.Int32])
I believe that this happens because you are attempting to map an IEnumerable to a List.
You either add a ToList() to the map expression after the Select. (not recommended)
Or declare the Locations property as an IEnumerable<int> in your Destination class.

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

Automapper Many to one map configuration

I want to map 3 different classes into a single DTO, each property have the same name on the source and the destination, the classes are the following:
User
Candidate
Portfolio
this is the DTO and how I want to map my objects:
public class CandidateTextInfo
{
public string ProfilePicture { get; set; } //-->User
public ObjectId UserId { get; set; } //-->User
public string Name { get; set; } //--> Candidate
public string Headline { get; set; } //--> Candidate
public Gender Gender { get; set; } //--> Candidate
public byte Rating { get; set; } //--> Candidate
public bool IsCompany { get; set; } //--> Candidate
public string[] Tags { get; set; } //--> Portafolio
public string[] Categories { get; set; } //--> Portafolio
public string ExecutiveSummary { get; set; } //--> Portafolio
public HourlyRate HourlyRate{ get; set; } //--> Candidate
}
I've been looking in SO and I found this solution but I don't get the method ConstructUsing
so how can I do to have a many to one mapping, is that possible, if not any workaround?
It depends greatly on the relationships between your objects. If you have a 1:1 relationship between your objects (e.g. if User has properties User.Candidate and User.Portfolio) then the mapping is easy:-
CreateMap<User, CandidateTextInfo>()
.ForMember(d => d.ProfilePicture, o => o.MapFrom(s => s.ProfilePicture)
// ...
.ForMember(d => d.Name, o => o.MapFrom(s => s.Candidate.Name)
// And so on...
If you don't have a one-to-one mapping, you need to arrange things a little bit yourself:-
public class CandidateTextInfoSource
{
public CandidateTextInfoSource(User user,
Candidate candidate,
Portafolio portafolio)
{
this.User = user;
this.Candidate = candidate;
this.Portafolio = portafolio;
}
public User User { get; set; }
public Candidate Candidate { get; set; }
public Portafolio Portafolio { get; set; }
}
// ...
CreateMap<CandidateTextInfoSource, CandidateTextInfo>()
.ForMember(d => d.ProfilePicture, o => o.MapFrom(s => s.User.ProfilePicture)
// ...
.ForMember(d => d.Name, o => o.MapFrom(s => s.Candidate.Name)
// And so on...
You can then use whatever means you require to create your CandidateTextInfoSource depending on the relationship between your objects. For example, if I assume that a User has a collection User.Candidates, and a Candidate has a property Candidate.Portfolio:-
CreateMap<User, IEnuemerable<CandidateTextInfoSource>>()
.ConstructUsing(
x => x.Candidates
.Select(y => Mapper.Map<CandidateTextInfo>(new CandidateTextInfoSource(x, y, y.Portfolio)))
.ToList());
I appreciate that this answer is very late, but if you further specify the relationship between your objects, I can help you create a more specific mapping.
Automapper's ConstructUsing is useful to build one property from custom code. In your case it is not really necessary. You just need to create the maps from your objects to your DTO. Then map each object instance to the same DTO instance.
However since Automapper wants each property of the destination object to be defined in order to ensure that the destination is fully specified you will need to configure each mapping with the properties not existing in the source object as ignored
CreateMap<Candidate, CandidateTextInfo>()
.ForMember(x=> x.ProfilePicture, opt => opt.Ignore())
.ForMember(...
// repeat for all destination properties not existing in source properties
If this is too much boilerplate code, many solutions are explored on stack overflow, among which this one looks promising: AutoMapper: "Ignore the rest"? (look at Robert Schroeder's answer)

Categories

Resources