Why this AutoMapper conversion fails? - c#

In the following automapper example I've to deal with some different kind of problem:
property of the target class (P) which are themselves classes (i.e. PContact, Bank)
lists (new List<Bank>())
wrapper class (Wrapper wraps IDataRecord)
What's wrong?
I would like to convert from an instance of a IDataRecord to an instance of class P.
AutoMapper.Mapper.CreateMap<IDataRecord, P>()
.ConvertUsing(x => AutoMapper.Mapper.Map<Wrapper, P>(new Wrapper(x)));
AutoMapper.Mapper.CreateMap<Wrapper, P>()
.ForMember(dest => dest.FirstName, opt => opt.MapFrom(src => src.GetString("Nome")))
.ForMember(dest => dest.LastName, opt => opt.MapFrom(src => src.GetString("Cognome")))
.ForMember(dest => dest.Banks,
opt => opt.ResolveUsing(src => AutoMapper.Mapper.Map<Wrapper, List<Bank>>(src)));
AutoMapper.Mapper.CreateMap<Wrapper, PContact>()
.ForMember(dest => dest.StreetAddress, opt => opt.MapFrom(src => src.GetString("Indirizzo")))
.ForMember(dest => dest.Town, opt => opt.MapFrom(src => src.GetString("Città")))
.ForMember(dest => dest.Phone, opt => opt.MapFrom(src => src.GetString("Telefono")))
AutoMapper.Mapper.CreateMap<Wrapper, List<Bank>>()
.ConvertUsing(x => new List<Bank>() { AutoMapper.Mapper.Map<Wrapper, Bank>(x) });
AutoMapper.Mapper.CreateMap<Wrapper, Bank>()
.ForMember(dest => dest.AccountNumber, opt => opt.MapFrom(src => src.GetString("NumCC")))
.ForMember(dest => dest.IBAN, opt => opt.MapFrom(src => src.GetString("IBAN")));

The problem was related to the fact that AutoMapper has its own Mapper for the IDataReader so it's needed to do the following
public class AutomapperTypeAdapterFactory
:ITypeAdapterFactory
{
public AutomapperTypeAdapterFactory()
{
//scan all assemblies finding Automapper Profile
var profiles = AppDomain.CurrentDomain
.GetAssemblies()
.SelectMany(a => a.GetTypes())
.Where(t => t.BaseType == typeof(Profile));
Mapper.Initialize(cfg =>
{
foreach (var item in profiles)
{
if (item.FullName != "AutoMapper.SelfProfiler`2")
cfg.AddProfile(Activator.CreateInstance(item) as Profile);
}
});
//===>>
MapperRegistry.Mappers.Remove(MapperRegistry.Mappers.FirstOrDefault(x => x is DataReaderMapper));
}
}

Related

How to configure automapper so that it won't override the properties which are not defined in mapping

I am mapping one class property with two different other classes using auto mapper like below code:
var emp = map.Map<EmployeeAddressDto, Employee>(employeeAddressDto);
The above mapping gives me objects like the below:
Address1 = "some xyz address1"
Address2 = "some xyz address2"
Phone = "0000000000"
and then mapping the output object with EmployeeOtherAddressDto
map.Map<EmployeeOtherAddressDto, Employee>(emp);
The above mapping changes my out to this:
Address1 = "some other xyz address1"
Address2 = "some other xyz address2"
Phone = null
But I want it to be like the below:
Address1 = "some other xyz address1"
Address2 = "some other xyz address2"
Phone = "0000000000"
When the above mapping occurs it makes the phone no null but I want it to remain as it was defined on the previous mapping, although I want to override Address1 and Address2 fields.
Here is my mapping:
CreateMap<EmployeeAddressDto, Employee>()
.ForMember(dest => dest.Address1, options => options.MapFrom(src => src.Address1))
.ForMember(dest => dest.Address2, options => options.MapFrom(src => src.Address2))
.ForMember(dest => dest.Phone, options => options.MapFrom(src => src.Phone))
.ValidateMemberList(MemberList.None)
.ForAllMembers(opts => opts.Condition((src, dest, srcMember) => srcMember != null));
CreateMap<EmployeeOtherAddressDto, Employee>()
.ForMember(dest => dest.Address1, options => options.MapFrom(src => src.Address1))
.ForMember(dest => dest.Address2, options => options.MapFrom(src => src.Address2))
.ValidateMemberList(MemberList.None)
.ForAllMembers(opts => opts.Condition((src, dest, srcMember) => srcMember != null));
I want to do this in my mapping profile because I am using it in many places and do not want to update the phone no without using a mapper.
Please try this method for config your mapping.
AfterMap<>()
CreateMap<EmployeeOtherAddressDto, Employee>().AfterMap<EmployeeMappingAction>()
.ForMember(dest => dest.Address1, options => options.MapFrom(src => src.Address1))
.ForMember(dest => dest.Address2, options => options.MapFrom(src => src.Address2))
.ValidateMemberList(MemberList.None)
you can check your filed after mapping to check is it null or not.
public class EmployeeMappingAction: IMappingAction<EmployeeOtherAddressDto,Employee>
{
public void Process(EmployeeOtherAddressDto source, Employee destination, ResolutionContext context)
{
if (source.Phone == null)
{
destination.Phone = "";
}
}
}

Handle multiple mappings for same source and destination object

I have Source class and Destination class to map. Destination class is a generic class such as Person which will be used as a field in one of my class (MainClass) in various fields e.g Father,Mother,Brother etc. How can I map data coming from source to destination which is present for Father,Mother etc.
I can create
CreateMap<Source, MainClass>()
.ForMember(dest => dest.Mother, m => m.MapFrom(source => source))
.ForMember(dest => dest.Father, m => m.MapFrom(source => source))
.ForMember(dest => dest.Brother, m => m.MapFrom(source => source));
Mapper.CreateMap<Source, Destination>()
.ForMember(dest => dest.Name, m => m.MapFrom(source => source.motherName))
.ForMember(dest => dest.ID, m => m.MapFrom(source => source.motherId))
.ForMember(dest => dest.Address, m => m.MapFrom(source => source.motherAddress));
but how can I handle mapping for father,brother etc. to achieve
Mapper.CreateMap<Source, Destination>()
.ForMember(dest => dest.Name, m => m.MapFrom(source => source.FatherName))
.ForMember(dest => dest.ID, m => m.MapFrom(source => source.FatherId))
.ForMember(dest => dest.Address, m => m.MapFrom(source => source.FatherAddress));
Ok so this is an untested Code from an notepad editor :D
you can try this and change it in the way you need it. This will not work from the very beginning!
opt.MapFrom(source => SetName(source, "Mother")))
private object SetName(Person y, string personState)
{
Person person = new Person();
var properties = DictionaryFromType(y);
foreach(var property in properties)
{
if(property.Key.ToLower().Contains(personState.ToLower()))
{
// you should make the real mapping to id here. This is just example code on how it could work
PropertyInfo propertyInfo = person.GetType().GetProperty(property.Key);
propertyInfo.SetValue(person, Convert.ChangeType(property.Value, propertyInfo.PropertyType), null);
}
}
return person;
}
public static Dictionary<string, object> DictionaryFromType(object atype)
{
if (atype == null) return new Dictionary<string, object>();
Type t = atype.GetType();
PropertyInfo[] props = t.GetProperties();
Dictionary<string, object> dict = new Dictionary<string, object>();
foreach (PropertyInfo prp in props)
{
object value = prp.GetValue(atype, new object[] { });
dict.Add(prp.Name, value);
}
return dict;
}
may be you have to debug a little bit to make it work but somehow like this you can make it.
It is possible that there are better solutions with automapper, but i only had this in my mind for the moment.
Hope this can help you even if this is not a finished answer! (Sorry my time is low atm)

AutoMapper - mapping integers to int array

I'm trying to map integers from a database to an int array, but I'm getting the error: AutoMapper.AutoMapperConfigurationException: 'Custom configuration for members is only supported for top level individual members on a type.'
This is what I have:
Model:
public class Year
{
public int[] Months { get; set; } = new int[3];
}
Mapping:
CreateMap<DataRow, Year>()
.ForMember(dest => dest.Months[0], opt => opt.MapFrom(src => src["Jan"]))
.ForMember(dest => dest.Months[1], opt => opt.MapFrom(src => src["Feb"]))
.ForMember(dest => dest.Months[2], opt => opt.MapFrom(src => src["Mar"]))
Anyone know how to get this to work?
You will need to do something like this
.ForMember(dest => dest.Months, opt => opt.MapFrom(src => MapFromRow(src)))
where you have a method
int[] MapFromRow(DataRow src)
{
int months = new int[12];
months[0] = src["Jan"];
...
return months;
}
AutoMapper does not seem to support "dotting" into a property or indexing into an array.
If you want to go fully inline, you can do
.ForMember(dest => dest.Months, opt => opt.MapFrom(src => new int[]
{
(int)src["Jan"], (int)src["Feb"], ...
}))

AutoMapper 4.2 not ignoring properties in profile

In my Web API controller method, before I map the UpdatePlaceDTO to PlaceMaster, I make a database call to populate the properties that are not covered by the Map but for some reason AutoMapper makes those properties null.
var mappedPlaceMaster = _mapper.Map<PlaceMaster>(placeMasterDTO);
// mappedPlaceMaster.EntityId is null
I've tried many of the solutions to IgnoreExistingMembers but none of them work.
This is what I have
public class PlaceMapperProfile : Profile
{
protected override void Configure()
{
// TO DO: This Mapping doesnt work. Need to ignore other properties
//CreateMap<UpdatePlaceDto, PlaceMaster>()
// .ForMember(d => d.Name, o => o.MapFrom(s => s.Name))
// .ForMember(d => d.Description, o => o.MapFrom(s => s.Description))
// .ForMember(d => d.ParentPlaceId, o => o.MapFrom(s => s.ParentPlaceId))
// .ForMember(d => d.LeftBower, o => o.MapFrom(s => s.LeftBower))
// .ForMember(d => d.RightBower, o => o.MapFrom(s => s.RightBower)).IgnoreAllNonExisting();
}
}
This is the extension
public static IMappingExpression<TSource, TDestination> IgnoreAllNonExisting<TSource, TDestination>(this IMappingExpression<TSource, TDestination> expression)
{
foreach (var property in expression.TypeMap.GetUnmappedPropertyNames())
{
expression.ForMember(property, opt => opt.Ignore());
}
return expression;
}
I've used Modules to inject the mapper to my depedencies
protected override void Load(ContainerBuilder builder)
{
//register all profile classes in the calling assembly
var profiles =
from t in typeof(Navigator.ItemManagement.Data.MappingProfiles.PlaceMapperProfile).Assembly.GetTypes()
where typeof(Profile).IsAssignableFrom(t)
select (Profile)Activator.CreateInstance(t);
builder.Register(context => new MapperConfiguration(cfg =>
{
foreach (var profile in profiles)
{
cfg.AddProfile(profile);
}
})).AsSelf().SingleInstance();
builder.Register(c => c.Resolve<MapperConfiguration>().CreateMapper(c.Resolve))
.As<IMapper>()
.SingleInstance();
}
I've seen in some thread that _mapper.Map actually creates a new object so how we make it to sort of "add-on" to the existing property values?
Ok I found the solution. It was right in front of me but I didn't see it!
I just had to use an overload of the Map function which doesn't create a new instance of PlaceMaster rather it assigns the properties which are available in the map.
mappedPlaceMaster = _mapper.Map(placeMasterDTO, placeMasterFromDatabase);

customize a field in automapper

public string AdjustTimezoneForDisplay(DateTime date)
{
//.......
}
automapper config
Mapper.Initialize(config =>
{
config.CreateMap<EquipmentOther, KMEquipmentOthers>()
.ForMember(x=>x.Status,opt=>opt.Ignore())
.ForMember(x => x.CreatedOn, opt => opt.MapFrom(src =>src.CreatedOn));
}
what am trying to achieve is to adjust time zone for CreatedOn field like the below code
ObjKMEquipmentOthers.CreatedOn = this._Utility.AdjustTimezoneForDisplay(EquipmentOtherDetails.CreatedOn);
would anybody please help me to achieve this
ps:AdjustTimezoneForDisplay is function to adjust time zone and return with local time
this worked for me ,hope this will help anybody in future
.ForMember(x => x.CreatedOn, opt => opt.MapFrom(x => AdjustTimezoneForDisplay(x.CreatedOn)));
and ps : AdjustTimezoneForDisplay must be static one .(initialy it was non static this is my problem ),again mapping initilizer also be static
public static string AdjustTimezoneForDisplay(DateTime date)
{
//.......
}
public static void RegisterMaps()
{
Mapper.Initialize(config =>
{
config.CreateMap<EquipmentOther, KMEquipmentOthers>()
.ForMember(x=>x.Status,opt=>opt.Ignore())
.ForMember(x => x.CreatedOn, opt => opt.MapFrom(x => AdjustTimezoneForDisplay(x.CreatedOn)));
}
}

Categories

Resources