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));
Related
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)
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"], ...
}))
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);
Here's my code:
Mapper.CreateMap<Foo, Foo2>()
.ForMember(dest => dest.Bar, opt => opt.MapFrom(src => src.Bar == null ? new BarViewModel() : src.Bar))
Basically, "BarViewModel" has a parameterless ctor which sets up properties in the class.
So i'm trying to say to AutoMapper:
If the value is null, then use the ctor for the class. otherwise use the mapping you have in place
The above is giving me a C# compiler error. And i'm guessing a cast wouldn't work either.
So is there a AutoMapper trick to do this?
Worst case i could remove that mapping for that property, and just do:
var mapped = Mapper.Map<Foo,Foo2>(src);
if (mapped.Bar == null) mapped.Bar = new BarViewModel();
But that's a tad ugly.
Ideas?
You can use custom value resolver. The following should work:
Mapper.CreateMap<Foo, Foo2>()
.ForMember(dest => dest.Bar, opt => opt.ResolveUsing(src => src.Bar == null ? new Bar() : Mapper.Map<Bar,Bar2>(src.Bar)))
Now you can use .NullSubstitute() to replace NULL value to some custom value in Automapper, e.g.:
CreateMap<SMModel, VM_SMModel>()
.ForMember(d => d.myDate, o => o.NullSubstitute(new DateTime(2017,12,12)));
As of Automapper 8, ResolveUsing is no longer an option but inline Func's, IValueResolver and IMemberValueResolver are 😊.
Inline Func Example
Mapper.Initialize(cfg =>
{
cfg.CreateMap<Foo, FooViewModel>()
.ForMember(dest => dest.BarViewModel,
opt => opt.MapFrom((src, dest) =>
{
if (src.Bar == null)
return new BarViewModel ();
return Mapper.Map<Bar, BarViewModel>(src.Bar);
}));
cfg.CreateMap<Bar, BarViewModel>();
});
IMemberValueResolver Example
Mapper.Initialize(cfg =>
{
cfg.CreateMap<Foo, FooViewModel>()
.ForMember(dest => dest.BarViewModel,
opt => opt.MapFrom<NullBarResolver, Bar>(src => src.Bar));
cfg.CreateMap<Bar, BarViewModel>();
});
public class NullBarResolver : IMemberValueResolver<object, object, Bar, BarViewModel>
{
public BarViewModel Resolve(object source, object destination, Bar sourceMember,
BarViewModel destMember, ResolutionContext context)
{
if (sourceMember == null)
return new BarViewModel();
return Mapper.Map(sourceMember, destMember);
}
}
There's some good documentation on Custom Value Resolvers here.
Working demo
I don't get a compiler error for the following:
public class Foo
{
public Bar Bar { get; set; }
}
public class Foo2
{
public Bar Bar { get; set; }
}
public class Bar
{
public int Id { get; set; }
public Bar()
{
Id = 3;
}
}
CreateMap<Foo, Foo2>()
.ForMember(
dest => dest.Bar,
opt => opt.MapFrom(src => src.Bar == null ? new Bar() : src.Bar));
...so I'm wondering if the problem is not actually with your mapping?
This can be done with the PreCondition() method. Here's an extension method I wrote to do this more easily:
public static IMappingExpression<TSource, TDestination> MapIf<TSource, TDestination>(
this IMappingExpression<TSource, TDestination> map, Expression<Func<TDestination, object>> selector,
Func<TSource, bool> mapIfCondition, Expression<Func<TSource, object>> mapping)
{
map.ForMember(selector, c =>
{
c.MapFrom(mapping);
c.PreCondition(mapIfCondition);
});
return map;
}
Usage Example:
//if settings are null coming from the sender, then ignore them and keep the current settings
CreateMap<PresentationView, Presentation>()
.MapIf(x => x.Settings, x => x.Settings is not null, v => v.Settings!)
So I need to make copies of objects. I have a model here "place" that has a IList HasAndBelongsToMany Property that is being a pain. I need to take the field property and copy it too but it copies the reference only. Here is what i have
public class place : ActiveRecordBase<place>
{
public place() { }
private int place_id;
[PrimaryKey("place_id")]
virtual public int id
{
get { return place_id; }
set { place_id = value; }
}
private IList<fields> Fields;
[HasAndBelongsToMany(typeof(fields), Lazy = true, Table = "place_to_fields", ColumnKey = "place_id", ColumnRef = "field_id", NotFoundBehaviour = NotFoundBehaviour.Ignore, Cascade = ManyRelationCascadeEnum.AllDeleteOrphan)]
virtual public IList<fields> field
{
get { return Fields; }
set { Fields = value; }
}
}
And use automapper like this
place org = ActiveRecordBase<place>.Find(id);
Mapper.Reset();
Mapper.CreateMap<place, place>().ForMember(dest => dest.id, o => o.Ignore())
.ForMember(dest => dest.field, o => o.Ignore())
;
place copy = new place();
Mapper.Map(org, copy);
copy.SaveAndFlush();
Which works because i'm skiping the field. What i was hoping for was something more like:
Mapper.CreateMap<place, place>().ForMember(dest => dest.id, o => o.Ignore())
.ForMember(dest => dest.field.id, o => o.Ignore())
;
See the first line with .ForMember(dest => dest.id, o => o.Ignore()) is so that I don't copy the reference id for the place object. I need to do the same for the place property field. I need to ignore the id's and make new entries with the same values on the rest of its properties
You need to create mapping for field type and add "Ignore" option for field's id like you've alredy done for place type.
Mapper.CreateMap<fields, fields>().ForMember(dest => dest.id, o => o.Ignore());
Mapper.CreateMap<place, place>().ForMember(dest => dest.id, o => o.Ignore());