Reputation: 398
I would like to have a generic way to map properties only if they are changed and not the default value. So let's say you have these two classes
public class Source
{
public string Foo { get; set; } = "Foo";
public string Bar { get; set; } = "Bar";
}
public class Destination
{
public string Foo { get; set; } = "Foo of Destination";
public string Bar { get; set; } = "Bar of Destination";
}
Now I want to map Source to Destination and if the property of Source.Foo is still "Foo" it should change to "Foo of Destination", but when the value of Source.Foo is different from "Foo" (the default) it should really map the property. I could not find a way to do this with automapper.
[TestMethod]
public void ChangeAllProperties()
{
var source = new Source();
var destination = _mapper.Map<Destination>(source);
var defaultDestination = new Destination();
Assert.AreEqual(destination.Bar, defaultDestination.Bar); // Should be the Default Value of "Destination"
Assert.AreEqual(destination.Foo, defaultDestination.Foo); // Should be the Default Value of "Destination"
}
[TestMethod]
public void ChangeDefaultProperty()
{
var source = new Source();
source.Bar = "Custom Bar!";
var destination = _mapper.Map<Destination>(source);
var defaultDestination = new Destination();
Assert.AreEqual(destination.Bar, source.Bar); // Should be Value of Source
Assert.AreEqual(destination.Foo, defaultDestination.Foo); // Should be the Default Value of "Destination"
}
[TestMethod]
public void AlLChanged()
{
var source = new Source();
source.Foo = "Custom Foo!";
source.Bar = "Custom Bar!";
var destination = _mapper.Map<Destination>(source);
Assert.AreEqual(destination.Bar, source.Bar); // Should be Value of Source
Assert.AreEqual(destination.Foo, source.Foo); // Should be Value of Source
}
Upvotes: 1
Views: 1260
Reputation: 398
The opt.Condition() idea from Plamen Yordanov was the right path. I was able to create something more generic, so i dont need to define this for every member and checks the default value of the properties. This is the solution i came up with. It only checks the default value if the destination member also exists in the source with the same name and type.
public class FooBarProfile: Profile
{
public FooBarProfile()
{
CreateMap<Source, Destination>()
.ForAllMembers(opt => opt.Condition(src => OnlyIfChanged(src, opt.DestinationMember)));
// And if you need reversed...
CreateMap<Destination, Source>()
.ForAllMembers(opt => opt.Condition(src => OnlyIfChanged(src, opt.DestinationMember)));
}
private static bool OnlyIfChanged<TSource>(TSource src, MemberInfo destinationMember) where TSource : new()
{
// Check if DestinationMember exists in TSource (same Name & Type)
var sourceMember = typeof(TSource).GetMembers().FirstOrDefault(e => e.Name == destinationMember.Name && e.MemberType == destinationMember.MemberType);
if (sourceMember != null)
{
var defaultOfSource = new TSource();
// Map only if Value of Source is differnent than the default soruce value
return (GetValue(sourceMember,src)?.Equals(GetValue(sourceMember,defaultOfSource)) == false);
}
return true;
}
// Helper-Method to get Value from a MemberInfo
private static object GetValue(MemberInfo memberInfo, object forObject)
{
switch (memberInfo.MemberType)
{
case MemberTypes.Field:
return ((FieldInfo)memberInfo).GetValue(forObject);
case MemberTypes.Property:
return ((PropertyInfo)memberInfo).GetValue(forObject);
default:
throw new ArgumentException("Member is not Field or Property.", nameof(memberInfo));
}
}
}
Upvotes: -1
Reputation: 860
Check out Automapper conditional mapping - link
Basically, you can do something like this (from the samples):
var configuration = new MapperConfiguration(cfg => {
cfg.CreateMap<Foo,Bar>()
.ForMember(dest => dest.baz, opt => opt.Condition(src => (src.baz >= 0)));
});
opt.Condition()
makes sure your condition is met before actually doing any mapping of that particular member.
Upvotes: 2