Reputation: 19004
I have a bunch of XSD.exe-generated data contract classes which for all optional elements have a pair of C# properties like
int Amount {get; set;}
bool isAmountSpecified {get; set;}
On the other side of mapping arena I have a nullable int like
int? Amount {get; set;}
Ideally I'd like for AutoMapper to be able to recognize such patterns and know how to map things in both directions without me having to specify a mapping for each individual property. Is this possible?
Upvotes: 10
Views: 1351
Reputation: 1767
Here is example of how to map model with *Specified properties (source) to model with nullable properties (destination).
I've configured Condition method for all members, which will check if source property have corresponding *Specified property and if it does, then it will check it's value. If *Specified property returns false
, then condition is not met and mapping will get skipped.
You can do the same in other direction, but instead of reading *Specified property value you will have to set it.
public void Configure(IMapperConfigurationExpression cfg)
{
cfg.CreateMap<Source, Destination>()
.ForAllOtherMembers(opt => opt.PreCondition((srcObj, context) => IsSpecified(srcObj, context, opt)));
}
public static bool IsSpecified<TSource, TDestination, TMember>(TSource source, ResolutionContext context, IMemberConfigurationExpression<TSource, TDestination, TMember> opt)
{
var dstMemberPropertyInfo = opt.DestinationMember as PropertyInfo;
// if destination member not nullable, then assume that source member won't have *Specified property
if (!IsNullableType(dstMemberPropertyInfo.PropertyType))
return true;
var config = context.Mapper.ConfigurationProvider;
var map = config.FindTypeMapFor<TSource, TDestination>();
var propertyMap = map.PropertyMaps.FirstOrDefault(x => x.DestinationMember.Name == opt.DestinationMember.Name);
var sourceMembers = new Queue<MemberInfo>(propertyMap.SourceMembers);
var srcParentType = typeof(TSource);
var srcParentObj = source as object;
// get the source parent instance
while (sourceMembers.Count > 1) // the last item in queue is the SourceMember itself
{
var srcParentPropertyInfo = sourceMembers.Dequeue() as PropertyInfo;
srcParentType = srcParentPropertyInfo.PropertyType;
srcParentObj = srcParentPropertyInfo.GetValue(srcParentObj);
// the source parent is not defined, so we can skip this mapping
if (srcParentObj == null)
return false;
}
var srcMemberSpecifiedPropName = propertyMap.SourceMember.Name + "Specified";
var srcMemberSpecifiedProp = srcParentType.GetProperty(srcMemberSpecifiedPropName);
// if there is no *Specified property, then assume value is specified
return srcMemberSpecifiedProp == null || (bool)srcMemberSpecifiedProp.GetValue(srcParentObj);
}
private bool IsNullableType(Type type) => IsGenericType(type, typeof(Nullable<>));
private bool IsGenericType(Type type, Type genericType) => IsGenericType(type) && type.GetGenericTypeDefinition() == genericType;
private bool IsGenericType(Type type) => type.GetTypeInfo().IsGenericType;
Upvotes: -1
Reputation: 19004
OK, yesterday I've had a brief discussion with Jimmy Bogard, author of AutoMapper, and basically what I'm looking for is currently not possible. Support for such conventions will be implemented some time in the future (if I understood him correctly :) ).
Upvotes: 2
Reputation: 1063864
I honestly have no idea whether AutoMapper will do that (since I don't use AutoMapper much), but I know that protobuf-net supports both those patterns, so you could use Serializer.ChangeType<,>(obj)
to flip between them.
The current version is, however, pretty dependent on having attributes (such as [XmlElement(Order = n)]
) on the members - I don't know if that causes an issue? The in progress version supports vanilla types (without attributes), but that isn't complete yet (but soon).
Example:
[XmlType]
public class Foo
{
[XmlElement(Order=1)]
public int? Value { get; set; }
}
[XmlType]
public class Bar
{
[XmlElement(Order = 1)]
public int Value { get; set; }
[XmlIgnore]
public bool ValueSpecified { get; set; }
}
static class Program
{
static void Main()
{
Foo foo = new Foo { Value = 123 };
Bar bar = Serializer.ChangeType<Foo, Bar>(foo);
Console.WriteLine("{0}, {1}", bar.Value, bar.ValueSpecified);
foo = new Foo { Value = null };
bar = Serializer.ChangeType<Foo, Bar>(foo);
Console.WriteLine("{0}, {1}", bar.Value, bar.ValueSpecified);
bar = new Bar { Value = 123, ValueSpecified = true };
foo = Serializer.ChangeType<Bar, Foo>(bar);
Console.WriteLine(foo.Value);
bar = new Bar { Value = 123, ValueSpecified = false };
foo = Serializer.ChangeType<Bar, Foo>(bar);
Console.WriteLine(foo.Value);
}
}
Upvotes: 1