Handle multiple mappings for same source and destination object - c#

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)

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 = "";
}
}
}

AutoMapper map Dictionary with the help of injected service

I need something like this:
public myMappingProfile(IInjectableService myInjectableService)
{
CreateMap<Source, Destination>()
.ForMember(dest => dest.DestinationDictionary, opt => opt.MapFrom(src =>
{
var dict = new Dictionary<myEnum, string>();
foreach (var item in src.SourceDictionary)
{
dict.Add(item.Key, myInjectableService.BuildUrl(item.Value));
}
return dict;
}));
Dependency Injection of the service works fine. But Visual Studio shows the following error message:
A lambda expression with a statement body cannot be converted to an
expression tree
Then I changed the destination type from Dictionary to List> and tried to use the AfterMap method:
.ForMember(dest => dest.DestinationListOfKeyValuePair, opt => opt
.MapFrom(src => src.SourceDictionary))
.AfterMap((src, dest) => dest.DestinationListOfKeyValuePair
.ForEach(ti => ti.Value = myInjectableService.BuildUrl(ti.Value)));
But Visual Studio complains:
Property or indexer cannot be assigned to -- it is read only
Next try was a CustomResolver:
.ForMember(dest => dest.TenantImages, opt => opt.MapFrom<CustomResolver>())
public class CustomResolver : IValueResolver>>
{
private readonly IInjectableService _myInjectableService;
public CustomResolver(IInjectableService myInjectableService)
{
_myInjectableService = myInjectableService;
}
public List<KeyValuePair<MyEnum, string>> Resolve(
Source source,
Destination destination,
List<KeyValuePair<MyEnum, string>> destMember,
ResolutionContext context)
{
destMember = new List<KeyValuePair<MyEnum, string>>();
foreach (var entry in source.SourceDictionary)
{
destMember.Add(new KeyValuePair<myEnum, string>(entry.Key, _myInjectableService.BuildUrl(entry.Value)));
}
return destMember;
}
}
But the followin exception is thrown:
System.MissingMethodException : No parameterless constructor defined
for this object.
I don't know how to put the IInjectableService into the CustomResolver.
Any ideas how to solve this issue?
Thank you.
I think you have to use .ConvertUsing():
// Add this mapping, if the name of the property in source and destination type differ.
CreateMap<Source, Destination>()
.ForMember(dest => dest.DestinationDictionary, opt => opt.MapFrom(src => src.SourceDictionary));
// Add this mapping to create an instance of the dictionary, filled by the values from the source dictionary.
CreateMap</*type of source dictionary*/, Dictionary<myEnum, string>>()
.ConvertUsing(src =>
{
var dict = new Dictionary<myEnum, string>();
foreach (var item in src)
{
dict.Add(item.Key, myInjectableService.BuildUrl(item.Value));
}
return dict;
}));
Additionally you can bring your dictionary creation down to a LINQ one liner:
src.ToDictionary(item => item.Key, item => myInjectableService.BuildUrl(item.Value));

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

Why this AutoMapper conversion fails?

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

Automapper 2 - unflattening entity to VM

I have a flat POCO Entity and a structured View Model (that is used for rendering a form with custom EditorFor helpers).
Is there any easier way in Automapper 2 to do something like that:
cfg.CreateMap<TelcoDemand, TelcoDemandViewModel>()
.ForMember(d => d.Person, opt => opt.ResolveUsing(s => new PersonViewModel
{
FirstName = s.PersonFirstName,
LastName = s.PersonLastName,
Phone = s.PersonPhone,
Email = s.PersonEmail
}))
.ForMember(d => d.AddressContact, opt => opt.ResolveUsing(s => new AddressViewModel
{
City = s.AddressContactCity,
HouseNumber = s.AddressContactHouseNumber,
Street = s.AddressContactStreet,
Zip = s.AddressContactZip
}))
.ForMember(d => d.AddressCourier, opt => opt.ResolveUsing(s => new AddressViewModel
{
City = s.AddressCourierCity,
HouseNumber = s.AddressCourierHouseNumber,
Street = s.AddressCourierStreet,
Zip = s.AddressCourierZip
}))
.ForMember(d => d.Address, opt => opt.ResolveUsing(s => new AddressViewModel
{
City = s.AddressCity,
HouseNumber = s.AddressHouseNumber,
Street = s.AddressStreet,
Zip = s.AddressZip
}))
What I am trying to achieve is to unflatten Person and Address fields into a viewmodel that then gets rendered using a EditorFor.
Flattening it back to database works great, but there is no way I could get the unflattening working by convention...
Edit
I have also a IgnoreAllUnexisting extension used here, it's implemented like that:
public static IMappingExpression<TSource, TDestination> IgnoreAllNonExisting<TSource, TDestination>(this IMappingExpression<TSource, TDestination> expression)
{
var sourceType = typeof(TSource);
var destinationType = typeof(TDestination);
var existingMaps = Mapper.GetAllTypeMaps().First(x => x.SourceType == sourceType && x.DestinationType == destinationType);
foreach (var property in existingMaps.GetUnmappedPropertyNames())
{
expression.ForMember(property, opt => opt.Ignore());
}
expression.ForAllMembers(opt => opt.Condition(src => !src.IsSourceValueNull));
return expression;
}
Anyway I also tried it without that extension, and the result was just the same - bunch of null values in the complex types.

Categories

Resources