Automapper: Trying to map to a property with a private setter - c#

I am trying to map to a class that contains a property with a private setter. I read in a different answer that this was possible (Automapper apparently used Reflection to do so) just as long as I configured the property in question with the ForMember method (see below)
Mapper config
var config = new MapperConfiguration(cfg => {
cfg.CreateMap<IDbCoverage, ICoverage>()
.ForMember(dest => dest.CoverageCodeDesc, conf => conf.MapFrom(src => src.CoverageCodeDesc));
});
Interfaces
public interface IDbCoverage
{
string ExternalMemberId { get; set; }
string CoverageCode { get; set; }
string CoverageCodeDesc { get; }
}
public interface ICoverage
{
string ExternalMemberId { get; set; }
string CoverageCode { get; set; }
string CoverageCodeDesc { get; }
}
Instantiation of the destination class:
public class Coverage
{
public string ExternalMemberId { get; set; }
public string CoverageCode { get; set; }
public string CoverageCodeDesc { get; private set; }
}
However when I try this only the properties with public setters are mapped. The source value of the other property is not mapped and the destination class ends up with a null value.
What am I doing wrong?

Related

Automapper executes without error, but no data being copied from source to destination

I have a class like this
public class ListOfBMTTeamMapping
{
public class TeamMapping
{
public List<TeamMappings> results { get; set; }
}
public class TeamMappings
{
public int id { get; set; }
public string areaPath { get; set; }
public string agileReleaseTrainName { get; set; }
public string deliveryTeamName { get; set; }
public string keyedInTeamCode { get; set; }
public string deliveryTeamId { get; set; }
public bool isDeleted { get; set; }
public string modified { get; set; }
public string modifiedBy { get; set; }
}
}
And here is my model class to which I need the above API class to get copied
public class JsonBmtAdoMapping
{
public int? Id { get; set; }
public string AreaPath { get; set; }
public string AgileReleaseTrainName { get; set; }
public string DeliveryTeamName { get; set; }
public string KeyedInTeamCode { get; set; }
public string DeliveryTeamId { get; set; }
public string IsDeleted { get; set; }
public DateTime? Modified { get; set; }
public string ModifiedBy { get; set; }
}
So here is my code I tried
var format = "dd/MM/yyyy";
var dateTimeConverter = new IsoDateTimeConverter { DateTimeFormat = format };
ListOfBMTTeamMapping.TeamMapping Results = new ListOfBMTTeamMapping.TeamMapping();
Results = JsonConvert.DeserializeObject<ListOfBMTTeamMapping.TeamMapping>(responseBody);
List<JsonBmtAdoMapping> jM = new List<JsonBmtAdoMapping>();
jM = _mapper.Map<ListOfBMTTeamMapping.TeamMapping,List<JsonBmtAdoMapping>>(Results);
int n = 10;
And here is my automapper profile
CreateMap<ListOfBMTTeamMapping.TeamMapping, List<JsonBmtAdoMapping>>();
CreateMap<ListOfBMTTeamMapping.TeamMappings, JsonBmtAdoMapping>();
But when the code executes, Ofcourse I am getting the data in results variable without any trouble
But when the mapper code fires, it execute the line without any error, but no data being copied from source to my model class which is the destination
jM.count is always 0 when Results hold 124 rows of data
What I did wrong
Your mapping from TeamMapping to List<JsonBmtAdoMapping> can't be done out of the box by AutoMapper, because your source is an object with a property that contains the list and the destination is a list on itself.
So you have to tell him, how this conversion from a single object to a list can be done. Due to the fact, that you already have a mapping for each individual item, we can use that recursively within our mapping method.
By using this mapping, it should work:
CreateMap<ListOfBMTTeamMapping.TeamMappings, JsonBmtAdoMapping>();
CreateMap<ListOfBMTTeamMapping.TeamMapping, List<JsonBmtAdoMapping>>()
.ConvertUsing((src, _, context) => src.results.Select(context.Mapper.Map<JsonBmtAdoMapping>).ToList());
Update
Cause a mapper is already defined for the individual items and lists are handled automatically by AutoMapper we can even make it shorter (thanks for Lucian for the hint in the comments):
CreateMap<ListOfBMTTeamMapping.TeamMappings, JsonBmtAdoMapping>();
CreateMap<ListOfBMTTeamMapping.TeamMapping, List<JsonBmtAdoMapping>>()
.ConvertUsing((src, _, context) => context.Mapper.Map<List<JsonBmtAdoMapping>>(src.results));

Automapper configure 2 classes to one

I have the following DB (Infrastructure) classes:
[Table("ApplicationDriverEquipments")]
public partial class ApplicationDriverEquipment
{
public int Id { get; set; }
[StringLength(256)]
public string Make { get; set; }
[StringLength(256)]
public string Model { get; set; }
[StringLength(256)]
public string Year { get; set; }
[StringLength(256)]
public string VINNumber { get; set; }
[StringLength(256)]
public string PlateNumber { get; set; }
[StringLength(256)]
public string CurrentMileage { get; set; }
[StringLength(256)]
public string Length { get; set; }
public int TypeId { get; set; }
public virtual ApplicationDriverEquipmentType Type { get; set; }
public int DriverId { get; set; }
public virtual ApplicationDriver Driver { get; set; }
}
[Table("ApplicationDriverEquipmentTypes")]
public partial class ApplicationDriverEquipmentType
{
public ApplicationDriverEquipmentType()
{
Equipments = new HashSet<ApplicationDriverEquipment>();
}
public int Id { get; set; }
[Required]
[StringLength(256)]
public string Name { get; set; }
public virtual ICollection<ApplicationDriverEquipment> Equipments { get; set; }
}
and the following DTO (Domain) classes:
public abstract class ApplicationDriverEquipmentAbstractDomain
{
public int Id { get; set; }
public string Make { get; set; }
public string Model { get; set; }
public string Year { get; set; }
public string PlateNumber { get; set; }
public string CurrentMileage { get; set; }
public string Type { get; protected set; }
}
public class ApplicationDriverEquipmentTractorDomain : ApplicationDriverEquipmentAbstractDomain
{
public ApplicationDriverEquipmentTractorDomain()
{
Type = ApplicationDriverEquipmentTypeStaticStringsDomain.Tractor;
}
public string VINNumber { get; set; }
}
public class ApplicationDriverEquipmentTrailerDomain : ApplicationDriverEquipmentAbstractDomain
{
public ApplicationDriverEquipmentTrailerDomain()
{
Type = ApplicationDriverEquipmentTypeStaticStringsDomain.Trailer;
}
public string Length { get; set; }
}
public class ApplicationDriverEquipmentStraightTruckDomain : ApplicationDriverEquipmentAbstractDomain
{
public ApplicationDriverEquipmentStraightTruckDomain()
{
Type = ApplicationDriverEquipmentTypeStaticStringsDomain.StraightTruck;
}
public string VINNumber { get; set; }
public string Length { get; set; }
}
public class ApplicationDriverEquipmentCargoVanDomain : ApplicationDriverEquipmentAbstractDomain
{
public ApplicationDriverEquipmentCargoVanDomain()
{
Type = ApplicationDriverEquipmentTypeStaticStringsDomain.CargoVan;
}
public string VINNumber { get; set; }
public string Length { get; set; }
}
public static class ApplicationDriverEquipmentTypeStaticStringsDomain
{
public const string Tractor = "Tractor";
public const string Trailer = "Trailer";
public const string StraightTruck = "Straight Truck";
public const string CargoVan = "Cargo Van";
}
I wrote the following Automapper rules to resolve it:
CreateMap<Domain.POCO.Application.ApplicationDriverEquipmentTractorDomain, Infrastructure.Asset.ApplicationDriverEquipment>()
.ForMember(c => c.Type.Name, p => p.UseValue<string>(Domain.StaticStrings.ApplicationDriverEquipmentTypeStaticStringsDomain.Tractor));
CreateMap<Domain.POCO.Application.ApplicationDriverEquipmentTrailerDomain, Infrastructure.Asset.ApplicationDriverEquipment>()
.ForMember(c => c.Type.Name, p => p.UseValue<string>(Domain.StaticStrings.ApplicationDriverEquipmentTypeStaticStringsDomain.Trailer));
CreateMap<Domain.POCO.Application.ApplicationDriverEquipmentStraightTruckDomain, Infrastructure.Asset.ApplicationDriverEquipment>()
.ForMember(c => c.Type.Name, p => p.UseValue<string>(Domain.StaticStrings.ApplicationDriverEquipmentTypeStaticStringsDomain.StraightTruck));
CreateMap<Domain.POCO.Application.ApplicationDriverEquipmentCargoVanDomain, Infrastructure.Asset.ApplicationDriverEquipment>()
.ForMember(c => c.Type.Name, p => p.UseValue<string>(Domain.StaticStrings.ApplicationDriverEquipmentTypeStaticStringsDomain.CargoVan));
I got an error:
Expression 'c => c.Type.Name' must resolve to top-level member and not
any child object's properties. Use a custom resolver on the child type
or the AfterMap option instead.
UPDATE
I rewrote maps:
CreateMap<Domain.POCO.Application.ApplicationDriverEquipmentTractorDomain, Infrastructure.Asset.ApplicationDriverEquipment>()
.AfterMap((src, dest)=> dest.Type.Name = Domain.StaticStrings.ApplicationDriverEquipmentTypeStaticStringsDomain.Tractor);
CreateMap<Domain.POCO.Application.ApplicationDriverEquipmentTrailerDomain, Infrastructure.Asset.ApplicationDriverEquipment>()
.AfterMap((src, dest) => dest.Type.Name = Domain.StaticStrings.ApplicationDriverEquipmentTypeStaticStringsDomain.Trailer);
CreateMap<Domain.POCO.Application.ApplicationDriverEquipmentStraightTruckDomain, Infrastructure.Asset.ApplicationDriverEquipment>()
.AfterMap((src, dest) => dest.Type.Name = Domain.StaticStrings.ApplicationDriverEquipmentTypeStaticStringsDomain.StraightTruck);
CreateMap<Domain.POCO.Application.ApplicationDriverEquipmentCargoVanDomain, Infrastructure.Asset.ApplicationDriverEquipment>()
.AfterMap((src, dest) => dest.Type.Name = Domain.StaticStrings.ApplicationDriverEquipmentTypeStaticStringsDomain.CargoVan);
but now I got an error:
Type Map configuration: ApplicationDriverEquipmentTractorDomain ->
ApplicationDriverEquipment
Domain.POCO.Application.ApplicationDriverEquipmentTractorDomain ->
Infrastructure.Asset.ApplicationDriverEquipment
Property: Type ---> AutoMapper.AutoMapperMappingException: Missing
type map configuration or unsupported mapping.
Mapping types:
String -> ApplicationDriverEquipmentType
System.String -> Infrastructure.Asset.ApplicationDriverEquipmentType
Seems, I don't understand how to map it correctly
You are trying to map from
ApplicationDriverEquipmentTractorDomain.Type is a string
to
ApplicationDriverEquipment.Type is a ApplicationDriverEquipmentType
Where is your mapping configuration for that?
Is it even possible to map a string to a ApplicationDriverEquipmentType?
Sure, you can have a string Name, but where do you get the Id and Equipments?
I suspect you don't want to create a new instance of that type each time you map, but rather you need to look up an instance from some dictionary, sort of a registry pattern
To implement this idea, you simply need to
Load all of the ApplicationDriverEquipmentType from DB
Put them in a dictionary (assuming name is unique)
Register a custom type converter or custom value resolver as below
One way to implement this would be to use a custom type converter
You could use something like
void ConvertUsing(Func<TSource, TDestination> mappingFunction);
And put in your own function that would resolve your ApplicationDriverEquipmentType by name, assuming name is unique like this:
var applicationEquipments = new ApplicationDriverEquipmentTypeRepository().FindAll(); // get all the values somehow from db
var dictionary = applicationEquipments.ToDictionary(x=>x.Name);
Func<string, ApplicationDriverEquipmentType> resolver = x=>dictionary[x];
Yet another way to do this would be to use a custom value resolver
Essentially, the idea would be the same - map of pre-loaded objects, only the way you "plug it in" would be different
Try to use MapFrom method instead:
.ForMember(c => c.Type.Name, p => p.MapFrom(s => Domain.StaticStrings.ApplicationDriverEquipmentTypeStaticStringsDomain.Tractor));

AutoMapper - Creating map for an interface member

I have an interface
internal interface IAttributeModel
{
string Name { get; set; }
int Id { get; set; }
string AttType { get; set; }
}
And a class implementing the interface
public class Currency:IAttributeModel
{
private string _type;
public Currency()
{
_type = "Currency";
}
public string Name { get; set; }
public int Id { get; set; }
string IAttributeModel.AttType
{
get { return _type; }
set { _type = value; }
}
}
Above class has implemented one property explicitly.
And my entity does look like the following.
public class ProviderAttribute
{
public int Id { get; set; }
public string Name { get; set; }
public string AttType { get; set; }
}
And I have created a simple mapping
Mapper.CreateMap<Entities.ProviderAttribute, Models.Currency>();
Mapper.CreateMap<Models.Currency, Entities.ProviderAttribute>();
Above mapping always setting overwriting Currency object's AttType property to null at the time of mapping. I this is probably happening because Currency is explicitly implementing IAttributeModel interface and my mapping is not able to find that.
How can I enforce my mapping to look into IAttributeModel interface.
Thanks.
You have to cast the Currency object to the Interface type IAttributeModel:
Mapper.Map((IAttributeModel)currency, providerAttribute);
And you have to let AutoMapper know how to map the interface:
Mapper.CreateMap<ProviderAttribute, Currency>();
Mapper.CreateMap<Currency, ProviderAttribute>();
Mapper.CreateMap<ProviderAttribute, IAttributeModel>();
Mapper.CreateMap<IAttributeModel, ProviderAttribute>();

Map flattened strings to list

Ok totally noob question about AutoMapper.
Employee model object.
public class Employee {
public string FirstName { get; set; }
public string LastName { get; set; }
public decimal HourlyRate { get; set; }
public List<string> EmailAddresses { get; set; }
}
Dto object:
public class UpdateEmployeeDetailsDto {
public decimal HourlyRate { get; set; }
public string EmailAddress1 { get; set; }
public string EmailAddress2 { get; set; }
}
I want to map the DTO to the Employee object and turn EmailAddress1 and EmailAddress2 into a List.
I've searched for this type of thing on google and SO but the expamples I've found I've not been able to translate them into working code.
Any help?
Use AfterMap to provide custom mapping (creation list from properties in your case):
Mapper.CreateMap<UpdateEmployeeDetailsDto, Employee>()
.AfterMap((dto, e) => e.EmailAddresses = new List<string>() {
dto.EmailAddress1,
dto.EmailAddress2
});
Apart from what #lazyberezovsky says, you can also create a custom resolver:
public class CustomResolver : ValueResolver<UpdateEmployeeDetailsDto, List<string>>
{
protected override List<string> ResolveCore(UpdateEmployeeDetailsDto source)
{
return new List<string> { source.EmailAddress1, source.EmailAddress2 }
}
}
Mapper.CreateMap<UpdateEmployeeDetailsDto, Employee>()
.ForMember(dest => dest.EmailAddresses, opt => opt.ResolveUsing<CustomResolver>());
What's the point? You can make it more generic and use this to resolve cases Value1, Value2, Value3...ValueN get mapped to List<ValueType> Values

Ignoring property in automapper for a nested class

I am having an issue where automapper is trying to map a property that I do not want it to map. I can fix this issue by renaming the property to something else but I want to use the name and I want to know how to fix issues like this.
My code looks like this.
public abstract class Task
{
public int Id { get; set; }
public int ProjectId { get; set; }
public virtual Project Project { get; set; }
public string Title { get; set; }
}
public class BasicTask : Task
{
}
public abstract class Project
{
public virtual int Id { get; set; }
public virtual string Title { get; set; }
public virtual ICollection<Task> Tasks { get; set; }
[NotMapped]
public abstract string ProjectTypeDescription { get; }
}
public class BasicProject : Project
{
public override string ProjectTypeDescription
{
get { return "Basic Project"; }
}
}
public abstract class TaskViewModel
{
public int Id { get; set; }
public string Title { get; set; }
public abstract string TaskType { get; }
//This is the property giving me issues
public ProjectDetails Project { get; set; }
public class ProjectDetails
{
public int Id { get; set; }
public string Title { get; set; }
}
}
public class BasicTaskViewModel : TaskViewModel
{
public override string TaskType
{
get { return "Basic"; }
}
}
public abstract class ProjectViewModel
{
public int Id { get; set; }
public string Title { get; set; }
public SelectList ProjectTypes { get; set; }
public abstract string ProjectType { get; }
public IEnumerable<TaskViewModel> Tasks { get; set; }
}
public class BasicProjectViewModel : ProjectViewModel
{
public override string ProjectType
{
get { return "Basic Project"; }
}
}
My mapping for Tasks looks like (I removed the project mapping since I can replicate the problem without those mappings)
Mapper.CreateMap<Task, TaskViewModel>()
.ForMember(dest => dest.Project, opt => opt.Ignore())
.Include<BasicTask, BasicTaskViewModel>();
Mapper.CreateMap<BasicTask, BasicTaskViewModel>();
Mapper.CreateMap<TaskViewModel, Task>()
.Include<BasicTaskViewModel, BasicTask>();
Mapper.CreateMap<BasicTaskViewModel, BasicTask>();
Mapper.CreateMap<Project, TaskViewModel.ProjectDetails>();
I am use an extenstion method for the mapping
public static TResult MapTo<TResult>(this object self)
{
return (TResult)Mapper.Map(self, self.GetType(), typeof(TResult));
}
An example of using this that fails is
TaskViewModel vm = new BasicTaskViewModel()
{
Id = 1,
Project = new TaskViewModel.ProjectDetails()
{
Id = 1,
Title = "Some Title",
}
};
Task m = vm.MapTo<Task>();
Sorry for the very long post I just don't know where the problem is.
If I rename Project in the taskviewmodel to something else it works and doesn't map TaskViewModel.ProjectDetails to Project which is what I want.
I have tried adding ForSourceMember(src => src.Project, opt => opt.Ignore()) in every spot that I can
When I run this I get the follwoing
Missing type map configuration or unsupported mapping.
Mapping types:
ProjectDetails -> Project
amtest.ViewModel.TaskViewModel+ProjectDetails -> amtest.Models.Project
Destination path:
BasicTask.Project.Project
Source value:
amtest.ViewModel.TaskViewModel+ProjectDetails
Description: An unhandled exception occurred during the execution of the current web request. Please review the stack trace for more information about the error and where it originated in the code.
Exception Details: AutoMapper.AutoMapperMappingException: Missing type map configuration or unsupported mapping.
Mapping types:
ProjectDetails -> Project
amtest.ViewModel.TaskViewModel+ProjectDetails -> amtest.Models.Project
Destination path:
BasicTask.Project.Project
Source value:
amtest.ViewModel.TaskViewModel+ProjectDetails
Does it help when you add the ignore also to the mapping of the child class?
Mapper.CreateMap<BasicTaskViewModel, BasicTask>()
.ForMember(dest => dest.Project, opt => opt.Ignore());

Categories

Resources