AutoMapper Map If Not Null, Otherwise Custom Convert - c#

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

Related

AutoMapper - Map value if it's valid type

These are my mapping classes:
class X
{
public string Value {get; set;}
}
class Y
{
public bool? BoolValue {get; set;}
public DateTime? DateTimeValue {get; set;}
public int? IntValue {get; set;}
}
Now, I want to map X to Y, and convert X.Value to the proper type:
CreateMap<X, Y>()
.ForMember(dest => dest.BoolValue, op => op.MapFrom(src => bool.TryParse(src.Value, out bool res) ? src.Value : null))
.ForMember(dest => dest.DateTimeValue, op => op.MapFrom(src => DateTime.TryParse(src.Value, out DateTime res) ? src.Value : null))
.ForMember(dest => dest.IntValue, op => op.MapFrom(src => int.TryParse(src.Value, out int res) ? src.Value : null))
.ForAllOtherMembers(dest => dest.Ignore());
If X.Value is "true", then Y.BoolValue should be set to
true, Y.DateTimeValue and Y.IntValue should be set to null
If X.Value is "2022.07.05", then Y.DateTimeValue should be set to new DateTime(2022, 07, 05), Y.BoolValue and Y.IntValue should be set to null
If X.Value is "5", then Y.IntValue should be set to 5, Y.DateTimeValue and Y.BoolValue should be set to null
I am getting an error:
An expression tree may not contain an out argument variable declaration in bool x, DatTime res, int res.
Any ideas?
Solution 1: Use Func delegate as replacement for the lambda expression.
MapperConfiguration config = new MapperConfiguration(ctx => {
ctx.CreateMap<X, Y>()
.ForMember(dest => dest.BoolValue, op => op.MapFrom((X src, Y dest) =>
{
return bool.TryParse(src.Value, out bool res) ? src.Value : null;
}))
.ForMember(dest => dest.DateTimeValue, op => op.MapFrom((X src, Y dest) =>
{
return DateTime.TryParse(src.Value, out DateTime res) ? src.Value : null;
}))
.ForMember(dest => dest.IntValue, op => op.MapFrom((X src, Y dest) =>
{
return int.TryParse(src.Value, out int res) ? src.Value : null;
}))
.ForAllOtherMembers(dest => dest.Ignore());
});
Based from here,
void MapFrom(Expression<Func<TSource, TDestination>> mappingExpression);
Solution 2: Can also work with Custom Value Resolver.
MapperConfiguration config = new MapperConfiguration(ctx => {
ctx.CreateMap<X, Y>()
.ForMember(dest => dest.BoolValue, op => op.MapFrom<CustomValueResolver<bool?>>())
.ForMember(dest => dest.DateTimeValue, op => op.MapFrom<CustomValueResolver<DateTime?>>())
.ForMember(dest => dest.IntValue, op => op.MapFrom<CustomValueResolver<int?>>())
.ForAllOtherMembers(dest => dest.Ignore());
});
public class CustomValueResolver<T> : IValueResolver<X, Y, T?>
{
public T? Resolve(X source, Y destination, T? member, ResolutionContext context)
{
try
{
Type type = Nullable.GetUnderlyingType(typeof(T));
T? safeValue = source.Value != null
? (T?)Convert.ChangeType(source.Value, type)
: default;
return safeValue;
}
catch
{
return default;
}
}
}
Sample .NET Fiddle
From #Lucian's suggestion, this part:
Type type = Nullable.GetUnderlyingType(typeof(T));
T? safeValue = source.Value != null
? (T?)Convert.ChangeType(source.Value, type)
: default;
return safeValue;
can be simplified with AutoMapper:
return context.Mapper.Map<T>(source.Value);

Mapping with a condition

I want to map 2 objects based on a condition, if true mapp else ignore, the condition is not a part of source neither destination
var mapperconfig = new MapperConfiguration(cfg =>
{
cfg.CreateMap<Source, Destination>()
.ForMember(source => source.Titulaires,
opt => opt.Condition(titulaires.HasValue && titulaires == true));
....
});
the extension method Condition() accepts just a type related to source or destination.
AutoMapper allows you to add conditions to properties that must be met before that property will be mapped.
Eg.
public class Foo
{
public int baz;
}
public class Bar
{
public uint baz;
}
public class Program
{
public static void Main()
{
Mapper.CreateMap<Foo,Bar>().ForMember(dest => dest.baz, opt => opt.Condition(src => (src.baz >= 0)));
var foo1 = new Foo { baz=-1 };
var bar1 = Mapper.Map<Bar>(foo1);
Console.WriteLine("bar1.baz={0}", bar1.baz);
var foo2 = new Foo{ baz=100 };
var bar2 = Mapper.Map<Bar>(foo2);
Console.WriteLine("bar2.baz={0}", bar2.baz);
}
}
Also, they give Preconditions functionality
See this link Conditional Mapping
thank you all for your help, I find a way to test my conditions into my automapper configuration : MapperConfiguration mappConf = new MapperConfiguration(config => {
config.CreateMap()
.ForMember(destination => destination.member, option => option.Condition(item => _condition == true))........});

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

Using Automapper to make full copy of an object with IList properties

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

Categories

Resources