Reputation: 2104
I'm using automapper via DI and want to have generic rules for all the mappings found in my solution. Belows example is of a single class, however I've got hundreds of classes to maintain and therefore want to add it to the mapper, not the mapping profile. So far, I can handle Null values as follows :
cnfg.ForAllMembers(opts => opts.Condition((src, dest, srcMember) => srcMember != null)));
Which in my example below, allows for the null update to persist past all the subsiquent updates. What I want is a generic rule ( or statement) for the default value of Guids, DateTimeOffset and Int. In my example below, update 1 and 2 are lost once update 3 is mapped.
using System;
using Autofac;
using AutoMapper;
namespace AutoMapperProblem
{
public class Program
{
static void Main(string[] args)
{
var builder = new ContainerBuilder();
builder.RegisterInstance(new MapperConfiguration(cfg =>
{
cfg.AddMaps(typeof(Program).Assembly);
cfg.ForAllMaps((obj, cnfg) =>
cnfg.ForAllMembers(opts => opts.Condition((src, dest, srcMember) => srcMember != null)));
}).CreateMapper()).As<IMapper>().SingleInstance();
var container = builder.Build();
var mapper = container.Resolve<IMapper>();
var MainObject = new MainObject();
//this update persists pass all updates
var updateNull = new UpdateObject { NullGuid = Guid.NewGuid() };
mapper.Map(updateNull, MainObject);
var update1 = new UpdateObject { DateTimeOffset = DateTimeOffset.Now };
mapper.Map(update1, MainObject);
var update2 = new UpdateObject { Guid = Guid.NewGuid() };
mapper.Map(update2, MainObject);
var update3 = new UpdateObject { Int = 10 };
mapper.Map(update3, MainObject);
}
}
public class MappingProfile : Profile
{
public MappingProfile()
{
CreateMap<MainObject, UpdateObject>()
.ReverseMap();
}
}
public class MainObject
{
public Guid? NullGuid { get; set; }
public Guid Guid { get; set; }
public int Int { get; set; }
public DateTimeOffset DateTimeOffset { get; set; }
}
public class UpdateObject
{
public Guid? NullGuid { get; set; }
public Guid Guid { get; set; }
public int Int { get; set; }
public DateTimeOffset DateTimeOffset { get; set; }
}
}
I want it to work based on the type, eg ints, DateTimeOffsets and Guid's .
Heres a working example on DotNetFiddle : https://dotnetfiddle.net/Zh0ta6
Thanks for any help with this
Upvotes: 2
Views: 2713
Reputation: 19555
The problem is that you cannot check value types against null
in your opts.Condition()
check, as they always have a value. Also, you can't use the default
keyword directly in this specific context because the variable type is object
. That will turn default
to null
again, and fail.
Depending on what you want to do, you can check if the destination already have a non-default value and do NOT replace it with any source value, which might be the default value of the type (0
for int
, 0000...-000
for Guid
, etc.). The check could look like this:
cnfg.ForAllMembers(opts => opts.Condition((src, dest, srcMember, destMember) =>
{
object destDefaultValue = null;
if (destMember != null)
{
Type destType = destMember.GetType();
if (destType.IsValueType)
{
destDefaultValue = Activator.CreateInstance(destType);
}
else
{
destDefaultValue = null;
}
}
bool destinationHasNoValue = Object.Equals(destMember, destDefaultValue);
return destinationHasNoValue;
})
The first part is calculating the default value for the given destination value, based on the type of the destination value. The latter part is checking if the destination already have a non-default value or not. Consecutive calls of Map()
with the same target object will fill the "missing" property values until all the property values are set (in this example). If necessary, check the srcMember
value and it's default type as well to fine-tune when the value should be copied or not.
Upvotes: 1